[
  {
    "path": ".eslintignore",
    "content": "/build\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n    \"env\": {\n        \"browser\": true,\n        \"es2021\": true,\n        \"node\": true,\n        \"jest/globals\": true\n    },\n    \"extends\": \"eslint:recommended\",\n    \"overrides\": [\n    ],\n    \"parserOptions\": {\n        \"ecmaVersion\": \"latest\",\n        \"sourceType\": \"module\"\n    },\n    \"plugins\": [\"jest\"],\n    \"globals\": {\n        \"gl\": true,\n        \"GL\": true,\n        \"LS\": true,\n        \"Uint8Array\": true,\n        \"Uint32Array\": true,\n        \"Float32Array\": true,\n        \"LGraphCanvas\": true,\n        \"LGraph\": true,\n        \"LGraphNode\": true,\n        \"LiteGraph\": true,\n        \"LGraphTexture\": true,\n        \"Mesh\": true,\n        \"Shader\": true,\n        \"enableWebGLCanvas\": true,\n        \"vec2\": true,\n        \"vec3\": true,\n        \"vec4\": true,\n        \"DEG2RAD\": true,\n        \"isPowerOfTwo\": true,\n        \"cloneCanvas\": true,\n        \"createCanvas\": true,\n        \"hex2num\": true,\n        \"colorToString\": true,\n        \"showElement\": true,\n        \"quat\": true,\n        \"AudioSynth\": true,\n        \"SillyClient\": true\n    },\n    \"rules\": {\n        \"no-console\": \"off\",\n        \"no-empty\": \"warn\",\n        \"no-redeclare\": \"warn\",\n        \"no-inner-declarations\": \"warn\",\n        \"no-constant-condition\": \"warn\",\n        \"no-unused-vars\": \"warn\",\n        \"no-mixed-spaces-and-tabs\": \"warn\",\n        \"no-unreachable\": \"warn\",\n        \"curly\": [\"warn\", \"all\"]\n    }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules/\nnode_modules/*\nnpm-debug.log\ntemp/\ntemp/*\ncoverage/\n\n# Editors\n/.vscode/*\n!/.vscode/extensions.json\n*.bak\n.project"
  },
  {
    "path": ".npmrc",
    "content": "package-lock=false"
  },
  {
    "path": ".prettierrc",
    "content": "{\n    \"singleQuote\": false,\n    \"semi\": true,\n    \"tabWidth\": 4\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n    // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.\n    // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp\n\n    // List of extensions which should be recommended for users of this workspace.\n    \"recommendations\": [\"esbenp.prettier-vscode\", \"dbaeumer.vscode-eslint\"],\n    // List of extensions recommended by VS Code that should not be recommended for users of this workspace.\n    \"unwantedRecommendations\": []\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribution Rules\nThere are some simple rules that everyone should follow:\n\n### Do not commit files from build folder\n> I usually have horrible merge conflicts when I upload the build version that take me too much time to solve, but I want to keep the build version in the repo, so I guess it would be better if only one of us does the built, which would be me.\n> https://github.com/jagenjo/litegraph.js/pull/155#issuecomment-656602861\nThose files will be updated by owner.\n\n\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2013 by Javi Agenjo\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE.\r\n"
  },
  {
    "path": "README.md",
    "content": "# litegraph.js\r\n\r\nA library in Javascript to create graphs in the browser similar to Unreal Blueprints. Nodes can be programmed easily and it includes an editor to construct and tests the graphs.\r\n\r\nIt can be integrated easily in any existing web applications and graphs can be run without the need of the editor.\r\n\r\nTry it in the [demo site](https://tamats.com/projects/litegraph/editor).\r\n\r\n![Node Graph](imgs/node_graph_example.png \"WebGLStudio\")\r\n\r\n## Features\r\n- Renders on Canvas2D (zoom in/out and panning, easy to render complex interfaces, can be used inside a WebGLTexture)\r\n- Easy to use editor (searchbox, keyboard shortcuts, multiple selection, context menu, ...)\r\n- Optimized to support hundreds of nodes per graph (on editor but also on execution)\r\n- Customizable theme (colors, shapes, background)\r\n- Callbacks to personalize every action/drawing/event of nodes\r\n- Subgraphs (nodes that contain graphs themselves)\r\n- Live mode system (hides the graph but calls nodes to render whatever they want, useful to create UIs)\r\n- Graphs can be executed in NodeJS\r\n- Highly customizable nodes (color, shape, slots vertical or horizontal, widgets, custom rendering)\r\n- Easy to integrate in any JS application (one single file, no dependencies)\r\n- Typescript support\r\n\r\n## Nodes provided\r\nAlthough it is easy to create new node types, LiteGraph comes with some default nodes that could be useful for many cases:\r\n- Interface (Widgets)\r\n- Math (trigonometry, math operations)\r\n- Audio (AudioAPI and MIDI)\r\n- 3D Graphics (Postprocessing in WebGL)\r\n- Input (read Gamepad)\r\n\r\n## Installation\r\n\r\nYou can install it using npm \r\n```\r\nnpm install litegraph.js\r\n```\r\n\r\nOr downloading the ```build/litegraph.js``` and ```css/litegraph.css``` version from this repository.\r\n\r\n## First project ##\r\n\r\n```html\r\n<html>\r\n<head>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"litegraph.css\">\r\n\t<script type=\"text/javascript\" src=\"litegraph.js\"></script>\r\n</head>\r\n<body style='width:100%; height:100%'>\r\n<canvas id='mycanvas' width='1024' height='720' style='border: 1px solid'></canvas>\r\n<script>\r\nvar graph = new LGraph();\r\n\r\nvar canvas = new LGraphCanvas(\"#mycanvas\", graph);\r\n\r\nvar node_const = LiteGraph.createNode(\"basic/const\");\r\nnode_const.pos = [200,200];\r\ngraph.add(node_const);\r\nnode_const.setValue(4.5);\r\n\r\nvar node_watch = LiteGraph.createNode(\"basic/watch\");\r\nnode_watch.pos = [700,200];\r\ngraph.add(node_watch);\r\n\r\nnode_const.connect(0, node_watch, 0 );\r\n\r\ngraph.start()\r\n</script>\r\n</body>\r\n</html>\r\n```\r\n\r\n## How to code a new Node type\r\n\r\nHere is an example of how to build a node that sums two inputs:\r\n\r\n```javascript\r\n//node constructor class\r\nfunction MyAddNode()\r\n{\r\n  this.addInput(\"A\",\"number\");\r\n  this.addInput(\"B\",\"number\");\r\n  this.addOutput(\"A+B\",\"number\");\r\n  this.properties = { precision: 1 };\r\n}\r\n\r\n//name to show\r\nMyAddNode.title = \"Sum\";\r\n\r\n//function to call when the node is executed\r\nMyAddNode.prototype.onExecute = function()\r\n{\r\n  var A = this.getInputData(0);\r\n  if( A === undefined )\r\n    A = 0;\r\n  var B = this.getInputData(1);\r\n  if( B === undefined )\r\n    B = 0;\r\n  this.setOutputData( 0, A + B );\r\n}\r\n\r\n//register in the system\r\nLiteGraph.registerNodeType(\"basic/sum\", MyAddNode );\r\n\r\n```\r\n\r\nor you can wrap an existing function:\r\n\r\n```js\r\nfunction sum(a,b)\r\n{\r\n   return a+b;\r\n}\r\n\r\nLiteGraph.wrapFunctionAsNode(\"math/sum\",sum, [\"Number\",\"Number\"],\"Number\");\r\n```\r\n\r\n## Server side\r\n\r\nIt also works server-side using NodeJS although some nodes do not work in server (audio, graphics, input, etc).\r\n\r\n```js\r\nvar LiteGraph = require(\"./litegraph.js\").LiteGraph;\r\n\r\nvar graph = new LiteGraph.LGraph();\r\n\r\nvar node_time = LiteGraph.createNode(\"basic/time\");\r\ngraph.add(node_time);\r\n\r\nvar node_console = LiteGraph.createNode(\"basic/console\");\r\nnode_console.mode = LiteGraph.ALWAYS;\r\ngraph.add(node_console);\r\n\r\nnode_time.connect( 0, node_console, 1 );\r\n\r\ngraph.start()\r\n```\r\n\r\n\r\n## Projects using it\r\n\r\n### [comfyUI](https://github.com/comfyanonymous/ComfyUI)\r\n![screenshot](https://github.com/comfyanonymous/ComfyUI/blob/6efe561c2a7321501b1b27f47039c7616dda1860/comfyui_screenshot.png)\r\n\r\n### [webglstudio.org](http://webglstudio.org)\r\n\r\n![WebGLStudio](imgs/webglstudio.gif \"WebGLStudio\")\r\n\r\n### [MOI Elephant](http://moiscript.weebly.com/elephant-systegraveme-nodal.html)\r\n\r\n![MOI Elephant](imgs/elephant.gif \"MOI Elephant\")\r\n\r\n### Mynodes\r\n\r\n![MyNodes](imgs/mynodes.png \"MyNodes\")\r\n\r\n## Utils\r\n-----\r\n\r\nIt includes several commands in the utils folder to generate doc, check errors and build minifyed version.\r\n\r\n\r\n## Demo\r\n-----\r\nThe demo includes some examples of graphs. In order to try them you can visit [demo site](http://tamats.com/projects/litegraph/editor) or install it on your local computer, to do so you need `git`, `node` and `npm`. Given those dependencies are installed, run the following commands to try it out:\r\n```sh\r\n$ git clone https://github.com/jagenjo/litegraph.js.git\r\n$ cd litegraph.js\r\n$ npm install\r\n$ node utils/server.js\r\nExample app listening on port 80!\r\n```\r\nOpen your browser and point it to http://localhost:8000/. You can select a demo from the dropdown at the top of the page.\r\n\r\n## Feedback\r\n--------\r\n\r\nYou can write any feedback to javi.agenjo@gmail.com\r\n\r\n## Contributors\r\n\r\n- atlasan\r\n- kriffe\r\n- rappestad\r\n- InventivetalentDev\r\n- NateScarlet\r\n- coderofsalvation\r\n- ilyabesk\r\n- gausszhou\r\n\r\n\r\n\r\n"
  },
  {
    "path": "build/litegraph.core.js",
    "content": "//packer version\n\n\n(function(global) {\n    // *************************************************************\n    //   LiteGraph CLASS                                     *******\n    // *************************************************************\n\n    /**\n     * The Global Scope. It contains all the registered node classes.\n     *\n     * @class LiteGraph\n     * @constructor\n     */\n\n    var LiteGraph = (global.LiteGraph = {\n        VERSION: 0.4,\n\n        CANVAS_GRID_SIZE: 10,\n\n        NODE_TITLE_HEIGHT: 30,\n        NODE_TITLE_TEXT_Y: 20,\n        NODE_SLOT_HEIGHT: 20,\n        NODE_WIDGET_HEIGHT: 20,\n        NODE_WIDTH: 140,\n        NODE_MIN_WIDTH: 50,\n        NODE_COLLAPSED_RADIUS: 10,\n        NODE_COLLAPSED_WIDTH: 80,\n        NODE_TITLE_COLOR: \"#999\",\n        NODE_SELECTED_TITLE_COLOR: \"#FFF\",\n        NODE_TEXT_SIZE: 14,\n        NODE_TEXT_COLOR: \"#AAA\",\n        NODE_SUBTEXT_SIZE: 12,\n        NODE_DEFAULT_COLOR: \"#333\",\n        NODE_DEFAULT_BGCOLOR: \"#353535\",\n        NODE_DEFAULT_BOXCOLOR: \"#666\",\n        NODE_DEFAULT_SHAPE: \"box\",\n        NODE_BOX_OUTLINE_COLOR: \"#FFF\",\n        DEFAULT_SHADOW_COLOR: \"rgba(0,0,0,0.5)\",\n        DEFAULT_GROUP_FONT: 24,\n\n        WIDGET_BGCOLOR: \"#222\",\n        WIDGET_OUTLINE_COLOR: \"#666\",\n        WIDGET_TEXT_COLOR: \"#DDD\",\n        WIDGET_SECONDARY_TEXT_COLOR: \"#999\",\n\n        LINK_COLOR: \"#9A9\",\n        EVENT_LINK_COLOR: \"#A86\",\n        CONNECTING_LINK_COLOR: \"#AFA\",\n\n        MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops\n        DEFAULT_POSITION: [100, 100], //default node position\n        VALID_SHAPES: [\"default\", \"box\", \"round\", \"card\"], //,\"circle\"\n\n        //shapes are used for nodes but also for slots\n        BOX_SHAPE: 1,\n        ROUND_SHAPE: 2,\n        CIRCLE_SHAPE: 3,\n        CARD_SHAPE: 4,\n        ARROW_SHAPE: 5,\n        GRID_SHAPE: 6, // intended for slot arrays\n\n        //enums\n        INPUT: 1,\n        OUTPUT: 2,\n\n        EVENT: -1, //for outputs\n        ACTION: -1, //for inputs\n\n        NODE_MODES: [\"Always\", \"On Event\", \"Never\", \"On Trigger\"], // helper, will add \"On Request\" and more in the future\n        NODE_MODES_COLORS:[\"#666\",\"#422\",\"#333\",\"#224\",\"#626\"], // use with node_box_coloured_by_mode\n        ALWAYS: 0,\n        ON_EVENT: 1,\n        NEVER: 2,\n        ON_TRIGGER: 3,\n\n        UP: 1,\n        DOWN: 2,\n        LEFT: 3,\n        RIGHT: 4,\n        CENTER: 5,\n\n        LINK_RENDER_MODES: [\"Straight\", \"Linear\", \"Spline\"], // helper\n        STRAIGHT_LINK: 0,\n        LINEAR_LINK: 1,\n        SPLINE_LINK: 2,\n\n        NORMAL_TITLE: 0,\n        NO_TITLE: 1,\n        TRANSPARENT_TITLE: 2,\n        AUTOHIDE_TITLE: 3,\n        VERTICAL_LAYOUT: \"vertical\", // arrange nodes vertically\n\n        proxy: null, //used to redirect calls\n        node_images_path: \"\",\n\n        debug: false,\n        catch_exceptions: true,\n        throw_errors: true,\n        allow_scripts: false, //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits\n        use_deferred_actions: true, //executes actions during the graph execution flow\n        registered_node_types: {}, //nodetypes by string\n        node_types_by_file_extension: {}, //used for dropping files in the canvas\n        Nodes: {}, //node types by classname\n\t\tGlobals: {}, //used to store vars between graphs\n\n        searchbox_extras: {}, //used to add extra features to the search box\n        auto_sort_node_types: false, // [true!] If set to true, will automatically sort node types / categories in the context menus\n\t\t\n\t\tnode_box_coloured_when_on: false, // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback\n        node_box_coloured_by_mode: false, // [true!] nodebox based on node mode, visual feedback\n        \n        dialog_close_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false\n        dialog_close_on_mouse_leave_delay: 500,\n        \n        shift_click_do_break_link_from: false, // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys\n        click_do_break_link_to: false, // [false!]prefer false, way too easy to break links\n        \n        search_hide_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false\n        search_filter_enabled: false, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out]\n        search_show_all_on_open: true, // [true!] opens the results list when opening the search widget\n        \n        auto_load_slot_types: false, // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out]\n        \n\t\t// set these values if not using auto_load_slot_types\n        registered_slot_in_types: {}, // slot types for nodeclass\n        registered_slot_out_types: {}, // slot types for nodeclass\n        slot_types_in: [], // slot types IN\n        slot_types_out: [], // slot types OUT\n        slot_types_default_in: [], // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search\n\t\tslot_types_default_out: [], // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search\n\t\t\n\t\talt_drag_do_clone_nodes: false, // [true!] very handy, ALT click to clone and drag the new node\n\n\t\tdo_add_triggers_slots: false, // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this\n\t\t\n\t\tallow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentially, one by one\n\n\t\tmiddle_click_slot_add_default_node: false, //[true!] allows to create and connect a ndoe clicking with the third button (wheel)\n\t\t\n\t\trelease_link_on_empty_shows_menu: false, //[true!] dragging a link to empty space will open a menu, add from list, search or defaults\n\t\t\n        pointerevents_method: \"mouse\", // \"mouse\"|\"pointer\" use mouse for retrocompatibility issues? (none found @ now)\n        // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary)\n\n        ctrl_shift_v_paste_connect_unselected_outputs: false, //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes\n\n        // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers.\n        // use this if you must have node IDs that are unique across all graphs and subgraphs.\n        use_uuids: false,\n\n        /**\n         * Register a node class so it can be listed when the user wants to create a new one\n         * @method registerNodeType\n         * @param {String} type name of the node and path\n         * @param {Class} base_class class containing the structure of a node\n         */\n\n        registerNodeType: function(type, base_class) {\n            if (!base_class.prototype) {\n                throw \"Cannot register a simple object, it must be a class with a prototype\";\n            }\n            base_class.type = type;\n\n            if (LiteGraph.debug) {\n                console.log(\"Node registered: \" + type);\n            }\n\n            const classname = base_class.name;\n\n            const pos = type.lastIndexOf(\"/\");\n            base_class.category = type.substring(0, pos);\n\n            if (!base_class.title) {\n                base_class.title = classname;\n            }\n\n            //extend class\n            for (var i in LGraphNode.prototype) {\n                if (!base_class.prototype[i]) {\n                    base_class.prototype[i] = LGraphNode.prototype[i];\n                }\n            }\n\n            const prev = this.registered_node_types[type];\n            if(prev) {\n                console.log(\"replacing node type: \" + type);\n            }\n            if( !Object.prototype.hasOwnProperty.call( base_class.prototype, \"shape\") ) {\n                Object.defineProperty(base_class.prototype, \"shape\", {\n                    set: function(v) {\n                        switch (v) {\n                            case \"default\":\n                                delete this._shape;\n                                break;\n                            case \"box\":\n                                this._shape = LiteGraph.BOX_SHAPE;\n                                break;\n                            case \"round\":\n                                this._shape = LiteGraph.ROUND_SHAPE;\n                                break;\n                            case \"circle\":\n                                this._shape = LiteGraph.CIRCLE_SHAPE;\n                                break;\n                            case \"card\":\n                                this._shape = LiteGraph.CARD_SHAPE;\n                                break;\n                            default:\n                                this._shape = v;\n                        }\n                    },\n                    get: function() {\n                        return this._shape;\n                    },\n                    enumerable: true,\n                    configurable: true\n                });\n                \n\n                //used to know which nodes to create when dragging files to the canvas\n                if (base_class.supported_extensions) {\n                    for (let i in base_class.supported_extensions) {\n                        const ext = base_class.supported_extensions[i];\n                        if(ext && ext.constructor === String) {\n                            this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class;\n                        }\n                    }\n                }\n            }\n\n            this.registered_node_types[type] = base_class;\n            if (base_class.constructor.name) {\n                this.Nodes[classname] = base_class;\n            }\n            if (LiteGraph.onNodeTypeRegistered) {\n                LiteGraph.onNodeTypeRegistered(type, base_class);\n            }\n            if (prev && LiteGraph.onNodeTypeReplaced) {\n                LiteGraph.onNodeTypeReplaced(type, base_class, prev);\n            }\n\n            //warnings\n            if (base_class.prototype.onPropertyChange) {\n                console.warn(\n                    \"LiteGraph node class \" +\n                        type +\n                        \" has onPropertyChange method, it must be called onPropertyChanged with d at the end\"\n                );\n            }\n            \n            // TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types\n            if (this.auto_load_slot_types) {\n                new base_class(base_class.title || \"tmpnode\");\n            }\n        },\n\n        /**\n         * removes a node type from the system\n         * @method unregisterNodeType\n         * @param {String|Object} type name of the node or the node constructor itself\n         */\n        unregisterNodeType: function(type) {\n            const base_class =\n                type.constructor === String\n                    ? this.registered_node_types[type]\n                    : type;\n            if (!base_class) {\n                throw \"node type not found: \" + type;\n            }\n            delete this.registered_node_types[base_class.type];\n            if (base_class.constructor.name) {\n                delete this.Nodes[base_class.constructor.name];\n            }\n        },\n\n        /**\n        * Save a slot type and his node\n        * @method registerSlotType\n        * @param {String|Object} type name of the node or the node constructor itself\n        * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, ..\n        */\n        registerNodeAndSlotType: function(type, slot_type, out){\n            out = out || false;\n            const base_class =\n                type.constructor === String &&\n                this.registered_node_types[type] !== \"anonymous\"\n                    ? this.registered_node_types[type]\n                    : type;\n\n            const class_type = base_class.constructor.type;\n\n            let allTypes = [];\n            if (typeof slot_type === \"string\") {\n                allTypes = slot_type.split(\",\");\n            } else if (slot_type == this.EVENT || slot_type == this.ACTION) {\n                allTypes = [\"_event_\"];\n            } else {\n                allTypes = [\"*\"];\n            }\n\n            for (let i = 0; i < allTypes.length; ++i) {\n                let slotType = allTypes[i];\n                if (slotType === \"\") {\n                    slotType = \"*\";\n                }\n                const registerTo = out\n                    ? \"registered_slot_out_types\"\n                    : \"registered_slot_in_types\";\n                if (this[registerTo][slotType] === undefined) {\n                    this[registerTo][slotType] = { nodes: [] };\n                }\n                if (!this[registerTo][slotType].nodes.includes(class_type)) {\n                    this[registerTo][slotType].nodes.push(class_type);\n                }\n\n                // check if is a new type\n                if (!out) {\n                    if (!this.slot_types_in.includes(slotType.toLowerCase())) {\n                        this.slot_types_in.push(slotType.toLowerCase());\n                        this.slot_types_in.sort();\n                    }\n                } else {\n                    if (!this.slot_types_out.includes(slotType.toLowerCase())) {\n                        this.slot_types_out.push(slotType.toLowerCase());\n                        this.slot_types_out.sort();\n                    }\n                }\n            }\n        },\n        \n        /**\n         * Create a new nodetype by passing an object with some properties\n         * like onCreate, inputs:Array, outputs:Array, properties, onExecute\n         * @method buildNodeClassFromObject\n         * @param {String} name node name with namespace (p.e.: 'math/sum')\n         * @param {Object} object methods expected onCreate, inputs, outputs, properties, onExecute\n         */\n         buildNodeClassFromObject: function(\n            name,\n            object\n        ) {\n            var ctor_code = \"\";\n            if(object.inputs)\n            for(var i=0; i < object.inputs.length; ++i)\n            {\n                var _name = object.inputs[i][0];\n                var _type = object.inputs[i][1];\n                if(_type && _type.constructor === String)\n                    _type = '\"'+_type+'\"';\n                ctor_code += \"this.addInput('\"+_name+\"',\"+_type+\");\\n\";\n            }\n            if(object.outputs)\n            for(var i=0; i < object.outputs.length; ++i)\n            {\n                var _name = object.outputs[i][0];\n                var _type = object.outputs[i][1];\n                if(_type && _type.constructor === String)\n                    _type = '\"'+_type+'\"';\n                ctor_code += \"this.addOutput('\"+_name+\"',\"+_type+\");\\n\";\n            }\n            if(object.properties)\n            for(var i in object.properties)\n            {\n                var prop = object.properties[i];\n                if(prop && prop.constructor === String)\n                    prop = '\"'+prop+'\"';\n                ctor_code += \"this.addProperty('\"+i+\"',\"+prop+\");\\n\";\n            }\n            ctor_code += \"if(this.onCreate)this.onCreate()\";\n            var classobj = Function(ctor_code);\n            for(var i in object)\n                if(i!=\"inputs\" && i!=\"outputs\" && i!=\"properties\")\n                    classobj.prototype[i] = object[i];\n            classobj.title = object.title || name.split(\"/\").pop();\n            classobj.desc = object.desc || \"Generated from object\";\n            this.registerNodeType(name, classobj);\n            return classobj;\n        },\n        \n        /**\n         * 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.\n         * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output.\n         * @method wrapFunctionAsNode\n         * @param {String} name node name with namespace (p.e.: 'math/sum')\n         * @param {Function} func\n         * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type\n         * @param {String} return_type [optional] string with the return type, otherwise it will be generic\n         * @param {Object} properties [optional] properties to be configurable\n         */\n        wrapFunctionAsNode: function(\n            name,\n            func,\n            param_types,\n            return_type,\n            properties\n        ) {\n            var params = Array(func.length);\n            var code = \"\";\n            if(param_types !== null) //null means no inputs\n            {\n                var names = LiteGraph.getParameterNames(func);\n                for (var i = 0; i < names.length; ++i) {\n                    var type = 0;\n                    if(param_types)\n                    {\n                        //type = param_types[i] != null ? \"'\" + param_types[i] + \"'\" : \"0\";\n                        if( param_types[i] != null && param_types[i].constructor === String )\n                            type = \"'\" + param_types[i] + \"'\" ;\n                        else if( param_types[i] != null )\n                            type = param_types[i];\n                    } \n                    code +=\n                        \"this.addInput('\" +\n                        names[i] +\n                        \"',\" +\n                        type +\n                        \");\\n\";\n                }\n            }\n            if(return_type !== null) //null means no output\n            code +=\n                \"this.addOutput('out',\" +\n                (return_type != null ? (return_type.constructor === String ? \"'\" + return_type + \"'\" : return_type) : 0) +\n                \");\\n\";\n            if (properties) {\n                code +=\n                    \"this.properties = \" + JSON.stringify(properties) + \";\\n\";\n            }\n            var classobj = Function(code);\n            classobj.title = name.split(\"/\").pop();\n            classobj.desc = \"Generated from \" + func.name;\n            classobj.prototype.onExecute = function onExecute() {\n                for (var i = 0; i < params.length; ++i) {\n                    params[i] = this.getInputData(i);\n                }\n                var r = func.apply(this, params);\n                this.setOutputData(0, r);\n            };\n            this.registerNodeType(name, classobj);\n            return classobj;\n        },\n\n        /**\n         * Removes all previously registered node's types\n         */\n        clearRegisteredTypes: function() {\n            this.registered_node_types = {};\n            this.node_types_by_file_extension = {};\n            this.Nodes = {};\n            this.searchbox_extras = {};\n        },\n\n        /**\n         * Adds this method to all nodetypes, existing and to be created\n         * (You can add it to LGraphNode.prototype but then existing node types wont have it)\n         * @method addNodeMethod\n         * @param {Function} func\n         */\n        addNodeMethod: function(name, func) {\n            LGraphNode.prototype[name] = func;\n            for (var i in this.registered_node_types) {\n                var type = this.registered_node_types[i];\n                if (type.prototype[name]) {\n                    type.prototype[\"_\" + name] = type.prototype[name];\n                } //keep old in case of replacing\n                type.prototype[name] = func;\n            }\n        },\n\n        /**\n         * Create a node of a given type with a name. The node is not attached to any graph yet.\n         * @method createNode\n         * @param {String} type full name of the node class. p.e. \"math/sin\"\n         * @param {String} name a name to distinguish from other nodes\n         * @param {Object} options to set options\n         */\n\n        createNode: function(type, title, options) {\n            var base_class = this.registered_node_types[type];\n            if (!base_class) {\n                if (LiteGraph.debug) {\n                    console.log(\n                        'GraphNode type \"' + type + '\" not registered.'\n                    );\n                }\n                return null;\n            }\n\n            var prototype = base_class.prototype || base_class;\n\n            title = title || base_class.title || type;\n\n            var node = null;\n\n            if (LiteGraph.catch_exceptions) {\n                try {\n                    node = new base_class(title);\n                } catch (err) {\n                    console.error(err);\n                    return null;\n                }\n            } else {\n                node = new base_class(title);\n            }\n\n            node.type = type;\n\n            if (!node.title && title) {\n                node.title = title;\n            }\n            if (!node.properties) {\n                node.properties = {};\n            }\n            if (!node.properties_info) {\n                node.properties_info = [];\n            }\n            if (!node.flags) {\n                node.flags = {};\n            }\n            if (!node.size) {\n                node.size = node.computeSize();\n\t\t\t\t//call onresize?\n            }\n            if (!node.pos) {\n                node.pos = LiteGraph.DEFAULT_POSITION.concat();\n            }\n            if (!node.mode) {\n                node.mode = LiteGraph.ALWAYS;\n            }\n\n            //extra options\n            if (options) {\n                for (var i in options) {\n                    node[i] = options[i];\n                }\n            }\n\n\t\t\t// callback\n            if ( node.onNodeCreated ) {\n                node.onNodeCreated();\n            }\n            \n            return node;\n        },\n\n        /**\n         * Returns a registered node type with a given name\n         * @method getNodeType\n         * @param {String} type full name of the node class. p.e. \"math/sin\"\n         * @return {Class} the node class\n         */\n        getNodeType: function(type) {\n            return this.registered_node_types[type];\n        },\n\n        /**\n         * Returns a list of node types matching one category\n         * @method getNodeType\n         * @param {String} category category name\n         * @return {Array} array with all the node classes\n         */\n\n        getNodeTypesInCategory: function(category, filter) {\n            var r = [];\n            for (var i in this.registered_node_types) {\n                var type = this.registered_node_types[i];\n                if (type.filter != filter) {\n                    continue;\n                }\n\n                if (category == \"\") {\n                    if (type.category == null) {\n                        r.push(type);\n                    }\n                } else if (type.category == category) {\n                    r.push(type);\n                }\n            }\n\n            if (this.auto_sort_node_types) {\n                r.sort(function(a,b){return a.title.localeCompare(b.title)});\n            }\n\n            return r;\n        },\n\n        /**\n         * Returns a list with all the node type categories\n         * @method getNodeTypesCategories\n         * @param {String} filter only nodes with ctor.filter equal can be shown\n         * @return {Array} array with all the names of the categories\n         */\n        getNodeTypesCategories: function( filter ) {\n            var categories = { \"\": 1 };\n            for (var i in this.registered_node_types) {\n\t\t\t\tvar type = this.registered_node_types[i];\n                if ( type.category && !type.skip_list )\n                {\n\t\t\t\t\tif(type.filter != filter)\n\t\t\t\t\t\tcontinue;\n                    categories[type.category] = 1;\n                }\n            }\n            var result = [];\n            for (var i in categories) {\n                result.push(i);\n            }\n            return this.auto_sort_node_types ? result.sort() : result;\n        },\n\n        //debug purposes: reloads all the js scripts that matches a wildcard\n        reloadNodes: function(folder_wildcard) {\n            var tmp = document.getElementsByTagName(\"script\");\n            //weird, this array changes by its own, so we use a copy\n            var script_files = [];\n            for (var i=0; i < tmp.length; i++) {\n                script_files.push(tmp[i]);\n            }\n\n            var docHeadObj = document.getElementsByTagName(\"head\")[0];\n            folder_wildcard = document.location.href + folder_wildcard;\n\n            for (var i=0; i < script_files.length; i++) {\n                var src = script_files[i].src;\n                if (\n                    !src ||\n                    src.substr(0, folder_wildcard.length) != folder_wildcard\n                ) {\n                    continue;\n                }\n\n                try {\n                    if (LiteGraph.debug) {\n                        console.log(\"Reloading: \" + src);\n                    }\n                    var dynamicScript = document.createElement(\"script\");\n                    dynamicScript.type = \"text/javascript\";\n                    dynamicScript.src = src;\n                    docHeadObj.appendChild(dynamicScript);\n                    docHeadObj.removeChild(script_files[i]);\n                } catch (err) {\n                    if (LiteGraph.throw_errors) {\n                        throw err;\n                    }\n                    if (LiteGraph.debug) {\n                        console.log(\"Error while reloading \" + src);\n                    }\n                }\n            }\n\n            if (LiteGraph.debug) {\n                console.log(\"Nodes reloaded\");\n            }\n        },\n\n        //separated just to improve if it doesn't work\n        cloneObject: function(obj, target) {\n            if (obj == null) {\n                return null;\n            }\n            var r = JSON.parse(JSON.stringify(obj));\n            if (!target) {\n                return r;\n            }\n\n            for (var i in r) {\n                target[i] = r[i];\n            }\n            return target;\n        },\n\n        /*\n         * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670\n         */\n        uuidv4: function() {\n            return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,a=>(a^Math.random()*16>>a/4).toString(16));\n        },\n\n        /**\n         * Returns if the types of two slots are compatible (taking into account wildcards, etc)\n         * @method isValidConnection\n         * @param {String} type_a\n         * @param {String} type_b\n         * @return {Boolean} true if they can be connected\n         */\n        isValidConnection: function(type_a, type_b) {\n\t\t\tif (type_a==\"\" || type_a===\"*\") type_a = 0;\n\t\t\tif (type_b==\"\" || type_b===\"*\") type_b = 0;\n            if (\n                !type_a //generic output\n                || !type_b // generic input\n                || type_a == type_b //same type (is valid for triggers)\n                || (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION)\n            ) {\n                return true;\n            }\n\n            // Enforce string type to handle toLowerCase call (-1 number not ok)\n            type_a = String(type_a);\n            type_b = String(type_b);\n            type_a = type_a.toLowerCase();\n            type_b = type_b.toLowerCase();\n\n            // For nodes supporting multiple connection types\n            if (type_a.indexOf(\",\") == -1 && type_b.indexOf(\",\") == -1) {\n                return type_a == type_b;\n            }\n\n            // Check all permutations to see if one is valid\n            var supported_types_a = type_a.split(\",\");\n            var supported_types_b = type_b.split(\",\");\n            for (var i = 0; i < supported_types_a.length; ++i) {\n                for (var j = 0; j < supported_types_b.length; ++j) {\n                    if(this.isValidConnection(supported_types_a[i],supported_types_b[j])){\n\t\t\t\t\t//if (supported_types_a[i] == supported_types_b[j]) {\n                        return true;\n                    }\n                }\n            }\n\n            return false;\n        },\n\n        /**\n         * Register a string in the search box so when the user types it it will recommend this node\n         * @method registerSearchboxExtra\n         * @param {String} node_type the node recommended\n         * @param {String} description text to show next to it\n         * @param {Object} data it could contain info of how the node should be configured\n         * @return {Boolean} true if they can be connected\n         */\n        registerSearchboxExtra: function(node_type, description, data) {\n            this.searchbox_extras[description.toLowerCase()] = {\n                type: node_type,\n                desc: description,\n                data: data\n            };\n        },\n\n        /**\n         * Wrapper to load files (from url using fetch or from file using FileReader)\n         * @method fetchFile\n         * @param {String|File|Blob} url the url of the file (or the file itself)\n         * @param {String} type an string to know how to fetch it: \"text\",\"arraybuffer\",\"json\",\"blob\"\n         * @param {Function} on_complete callback(data)\n         * @param {Function} on_error in case of an error\n         * @return {FileReader|Promise} returns the object used to \n         */\n\t\tfetchFile: function( url, type, on_complete, on_error ) {\n\t\t\tvar that = this;\n\t\t\tif(!url)\n\t\t\t\treturn null;\n\n\t\t\ttype = type || \"text\";\n\t\t\tif( url.constructor === String )\n\t\t\t{\n\t\t\t\tif (url.substr(0, 4) == \"http\" && LiteGraph.proxy) {\n\t\t\t\t\turl = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\n\t\t\t\t}\n\t\t\t\treturn fetch(url)\n\t\t\t\t.then(function(response) {\n\t\t\t\t\tif(!response.ok)\n\t\t\t\t\t\t throw new Error(\"File not found\"); //it will be catch below\n\t\t\t\t\tif(type == \"arraybuffer\")\n\t\t\t\t\t\treturn response.arrayBuffer();\n\t\t\t\t\telse if(type == \"text\" || type == \"string\")\n\t\t\t\t\t\treturn response.text();\n\t\t\t\t\telse if(type == \"json\")\n\t\t\t\t\t\treturn response.json();\n\t\t\t\t\telse if(type == \"blob\")\n\t\t\t\t\t\treturn response.blob();\n\t\t\t\t})\n\t\t\t\t.then(function(data) {\n\t\t\t\t\tif(on_complete)\n\t\t\t\t\t\ton_complete(data);\n\t\t\t\t})\n\t\t\t\t.catch(function(error) {\n\t\t\t\t\tconsole.error(\"error fetching file:\",url);\n\t\t\t\t\tif(on_error)\n\t\t\t\t\t\ton_error(error);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if( url.constructor === File || url.constructor === Blob)\n\t\t\t{\n\t\t\t\tvar reader = new FileReader();\n\t\t\t\treader.onload = function(e)\n\t\t\t\t{\n\t\t\t\t\tvar v = e.target.result;\n\t\t\t\t\tif( type == \"json\" )\n\t\t\t\t\t\tv = JSON.parse(v);\n\t\t\t\t\tif(on_complete)\n\t\t\t\t\t\ton_complete(v);\n\t\t\t\t}\n\t\t\t\tif(type == \"arraybuffer\")\n\t\t\t\t\treturn reader.readAsArrayBuffer(url);\n\t\t\t\telse if(type == \"text\" || type == \"json\")\n\t\t\t\t\treturn reader.readAsText(url);\n\t\t\t\telse if(type == \"blob\")\n\t\t\t\t\treturn reader.readAsBinaryString(url);\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n    });\n\n    //timer that works everywhere\n    if (typeof performance != \"undefined\") {\n        LiteGraph.getTime = performance.now.bind(performance);\n    } else if (typeof Date != \"undefined\" && Date.now) {\n        LiteGraph.getTime = Date.now.bind(Date);\n    } else if (typeof process != \"undefined\") {\n        LiteGraph.getTime = function() {\n            var t = process.hrtime();\n            return t[0] * 0.001 + t[1] * 1e-6;\n        };\n    } else {\n        LiteGraph.getTime = function getTime() {\n            return new Date().getTime();\n        };\n    }\n\n    //*********************************************************************************\n    // LGraph CLASS\n    //*********************************************************************************\n\n    /**\n     * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.\n\t * supported callbacks:\n\t\t+ onNodeAdded: when a new node is added to the graph\n\t\t+ onNodeRemoved: when a node inside this graph is removed\n\t\t+ onNodeConnectionChange: some connection has changed in the graph (connected or disconnected)\n     *\n     * @class LGraph\n     * @constructor\n     * @param {Object} o data from previous serialization [optional]\n     */\n\n    function LGraph(o) {\n        if (LiteGraph.debug) {\n            console.log(\"Graph created\");\n        }\n        this.list_of_graphcanvas = null;\n        this.clear();\n\n        if (o) {\n            this.configure(o);\n        }\n    }\n\n    global.LGraph = LiteGraph.LGraph = LGraph;\n\n    //default supported types\n    LGraph.supported_types = [\"number\", \"string\", \"boolean\"];\n\n    //used to know which types of connections support this graph (some graphs do not allow certain types)\n    LGraph.prototype.getSupportedTypes = function() {\n        return this.supported_types || LGraph.supported_types;\n    };\n\n    LGraph.STATUS_STOPPED = 1;\n    LGraph.STATUS_RUNNING = 2;\n\n    /**\n     * Removes all nodes from this graph\n     * @method clear\n     */\n\n    LGraph.prototype.clear = function() {\n        this.stop();\n        this.status = LGraph.STATUS_STOPPED;\n\n        this.last_node_id = 0;\n        this.last_link_id = 0;\n\n        this._version = -1; //used to detect changes\n\n        //safe clear\n        if (this._nodes) {\n            for (var i = 0; i < this._nodes.length; ++i) {\n                var node = this._nodes[i];\n                if (node.onRemoved) {\n                    node.onRemoved();\n                }\n            }\n        }\n\n        //nodes\n        this._nodes = [];\n        this._nodes_by_id = {};\n        this._nodes_in_order = []; //nodes sorted in execution order\n        this._nodes_executable = null; //nodes that contain onExecute sorted in execution order\n\n        //other scene stuff\n        this._groups = [];\n\n        //links\n        this.links = {}; //container with all the links\n\n        //iterations\n        this.iteration = 0;\n\n        //custom data\n        this.config = {};\n\t\tthis.vars = {};\n\t\tthis.extra = {}; //to store custom data\n\n        //timing\n        this.globaltime = 0;\n        this.runningtime = 0;\n        this.fixedtime = 0;\n        this.fixedtime_lapse = 0.01;\n        this.elapsed_time = 0.01;\n        this.last_update_time = 0;\n        this.starttime = 0;\n\n        this.catch_errors = true;\n\n        this.nodes_executing = [];\n        this.nodes_actioning = [];\n        this.nodes_executedAction = [];\n        \n        //subgraph_data\n        this.inputs = {};\n        this.outputs = {};\n\n        //notify canvas to redraw\n        this.change();\n\n        this.sendActionToCanvas(\"clear\");\n    };\n\n    /**\n     * Attach Canvas to this graph\n     * @method attachCanvas\n     * @param {GraphCanvas} graph_canvas\n     */\n\n    LGraph.prototype.attachCanvas = function(graphcanvas) {\n        if (graphcanvas.constructor != LGraphCanvas) {\n            throw \"attachCanvas expects a LGraphCanvas instance\";\n        }\n        if (graphcanvas.graph && graphcanvas.graph != this) {\n            graphcanvas.graph.detachCanvas(graphcanvas);\n        }\n\n        graphcanvas.graph = this;\n\n        if (!this.list_of_graphcanvas) {\n            this.list_of_graphcanvas = [];\n        }\n        this.list_of_graphcanvas.push(graphcanvas);\n    };\n\n    /**\n     * Detach Canvas from this graph\n     * @method detachCanvas\n     * @param {GraphCanvas} graph_canvas\n     */\n    LGraph.prototype.detachCanvas = function(graphcanvas) {\n        if (!this.list_of_graphcanvas) {\n            return;\n        }\n\n        var pos = this.list_of_graphcanvas.indexOf(graphcanvas);\n        if (pos == -1) {\n            return;\n        }\n        graphcanvas.graph = null;\n        this.list_of_graphcanvas.splice(pos, 1);\n    };\n\n    /**\n     * Starts running this graph every interval milliseconds.\n     * @method start\n     * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate\n     */\n\n    LGraph.prototype.start = function(interval) {\n        if (this.status == LGraph.STATUS_RUNNING) {\n            return;\n        }\n        this.status = LGraph.STATUS_RUNNING;\n\n        if (this.onPlayEvent) {\n            this.onPlayEvent();\n        }\n\n        this.sendEventToAllNodes(\"onStart\");\n\n        //launch\n        this.starttime = LiteGraph.getTime();\n        this.last_update_time = this.starttime;\n        interval = interval || 0;\n        var that = this;\n\n\t\t//execute once per frame\n        if ( interval == 0 && typeof window != \"undefined\" && window.requestAnimationFrame ) {\n            function on_frame() {\n                if (that.execution_timer_id != -1) {\n                    return;\n                }\n                window.requestAnimationFrame(on_frame);\n\t\t\t\tif(that.onBeforeStep)\n\t\t\t\t\tthat.onBeforeStep();\n                that.runStep(1, !that.catch_errors);\n\t\t\t\tif(that.onAfterStep)\n\t\t\t\t\tthat.onAfterStep();\n            }\n            this.execution_timer_id = -1;\n            on_frame();\n        } else { //execute every 'interval' ms\n            this.execution_timer_id = setInterval(function() {\n                //execute\n\t\t\t\tif(that.onBeforeStep)\n\t\t\t\t\tthat.onBeforeStep();\n                that.runStep(1, !that.catch_errors);\n\t\t\t\tif(that.onAfterStep)\n\t\t\t\t\tthat.onAfterStep();\n            }, interval);\n        }\n    };\n\n    /**\n     * Stops the execution loop of the graph\n     * @method stop execution\n     */\n\n    LGraph.prototype.stop = function() {\n        if (this.status == LGraph.STATUS_STOPPED) {\n            return;\n        }\n\n        this.status = LGraph.STATUS_STOPPED;\n\n        if (this.onStopEvent) {\n            this.onStopEvent();\n        }\n\n        if (this.execution_timer_id != null) {\n            if (this.execution_timer_id != -1) {\n                clearInterval(this.execution_timer_id);\n            }\n            this.execution_timer_id = null;\n        }\n\n        this.sendEventToAllNodes(\"onStop\");\n    };\n\n    /**\n     * Run N steps (cycles) of the graph\n     * @method runStep\n     * @param {number} num number of steps to run, default is 1\n     * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors \n     * @param {number} limit max number of nodes to execute (used to execute from start to a node)\n     */\n\n    LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) {\n        num = num || 1;\n\n        var start = LiteGraph.getTime();\n        this.globaltime = 0.001 * (start - this.starttime);\n\n        //not optimal: executes possible pending actions in node, problem is it is not optimized\n        //it is done here as if it was done in the later loop it wont be called in the node missed the onExecute\n        \n        //from now on it will iterate only on executable nodes which is faster\n        var nodes = this._nodes_executable\n            ? this._nodes_executable\n            : this._nodes;\n        if (!nodes) {\n            return;\n        }\n\n\t\tlimit = limit || nodes.length;\n\n        if (do_not_catch_errors) {\n            //iterations\n            for (var i = 0; i < num; i++) {\n                for (var j = 0; j < limit; ++j) {\n                    var node = nodes[j];\n                    if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length)\n                        node.executePendingActions();\n                    if (node.mode == LiteGraph.ALWAYS && node.onExecute) {\n                        //wrap node.onExecute();\n\t\t\t\t\t\tnode.doExecute();\n                    }\n                }\n\n                this.fixedtime += this.fixedtime_lapse;\n                if (this.onExecuteStep) {\n                    this.onExecuteStep();\n                }\n            }\n\n            if (this.onAfterExecute) {\n                this.onAfterExecute();\n            }\n        } else { //catch errors\n            try {\n                //iterations\n                for (var i = 0; i < num; i++) {\n                    for (var j = 0; j < limit; ++j) {\n                        var node = nodes[j];\n                        if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length)\n                            node.executePendingActions();\n                        if (node.mode == LiteGraph.ALWAYS && node.onExecute) {\n                            node.onExecute();\n                        }\n                    }\n\n                    this.fixedtime += this.fixedtime_lapse;\n                    if (this.onExecuteStep) {\n                        this.onExecuteStep();\n                    }\n                }\n\n                if (this.onAfterExecute) {\n                    this.onAfterExecute();\n                }\n                this.errors_in_execution = false;\n            } catch (err) {\n                this.errors_in_execution = true;\n                if (LiteGraph.throw_errors) {\n                    throw err;\n                }\n                if (LiteGraph.debug) {\n                    console.log(\"Error during execution: \" + err);\n                }\n                this.stop();\n            }\n        }\n\n        var now = LiteGraph.getTime();\n        var elapsed = now - start;\n        if (elapsed == 0) {\n            elapsed = 1;\n        }\n        this.execution_time = 0.001 * elapsed;\n        this.globaltime += 0.001 * elapsed;\n        this.iteration += 1;\n        this.elapsed_time = (now - this.last_update_time) * 0.001;\n        this.last_update_time = now;\n        this.nodes_executing = [];\n        this.nodes_actioning = [];\n        this.nodes_executedAction = [];\n    };\n\n    /**\n     * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than\n     * nodes with only inputs.\n     * @method updateExecutionOrder\n     */\n    LGraph.prototype.updateExecutionOrder = function() {\n        this._nodes_in_order = this.computeExecutionOrder(false);\n        this._nodes_executable = [];\n        for (var i = 0; i < this._nodes_in_order.length; ++i) {\n            if (this._nodes_in_order[i].onExecute) {\n                this._nodes_executable.push(this._nodes_in_order[i]);\n            }\n        }\n    };\n\n    //This is more internal, it computes the executable nodes in order and returns it\n    LGraph.prototype.computeExecutionOrder = function(\n        only_onExecute,\n        set_level\n    ) {\n        var L = [];\n        var S = [];\n        var M = {};\n        var visited_links = {}; //to avoid repeating links\n        var remaining_links = {}; //to a\n\n        //search for the nodes without inputs (starting nodes)\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            var node = this._nodes[i];\n            if (only_onExecute && !node.onExecute) {\n                continue;\n            }\n\n            M[node.id] = node; //add to pending nodes\n\n            var num = 0; //num of input connections\n            if (node.inputs) {\n                for (var j = 0, l2 = node.inputs.length; j < l2; j++) {\n                    if (node.inputs[j] && node.inputs[j].link != null) {\n                        num += 1;\n                    }\n                }\n            }\n\n            if (num == 0) {\n                //is a starting node\n                S.push(node);\n                if (set_level) {\n                    node._level = 1;\n                }\n            } //num of input links\n            else {\n                if (set_level) {\n                    node._level = 0;\n                }\n                remaining_links[node.id] = num;\n            }\n        }\n\n        while (true) {\n            if (S.length == 0) {\n                break;\n            }\n\n            //get an starting node\n            var node = S.shift();\n            L.push(node); //add to ordered list\n            delete M[node.id]; //remove from the pending nodes\n\n            if (!node.outputs) {\n                continue;\n            }\n\n            //for every output\n            for (var i = 0; i < node.outputs.length; i++) {\n                var output = node.outputs[i];\n                //not connected\n                if (\n                    output == null ||\n                    output.links == null ||\n                    output.links.length == 0\n                ) {\n                    continue;\n                }\n\n                //for every connection\n                for (var j = 0; j < output.links.length; j++) {\n                    var link_id = output.links[j];\n                    var link = this.links[link_id];\n                    if (!link) {\n                        continue;\n                    }\n\n                    //already visited link (ignore it)\n                    if (visited_links[link.id]) {\n                        continue;\n                    }\n\n                    var target_node = this.getNodeById(link.target_id);\n                    if (target_node == null) {\n                        visited_links[link.id] = true;\n                        continue;\n                    }\n\n                    if (\n                        set_level &&\n                        (!target_node._level ||\n                            target_node._level <= node._level)\n                    ) {\n                        target_node._level = node._level + 1;\n                    }\n\n                    visited_links[link.id] = true; //mark as visited\n                    remaining_links[target_node.id] -= 1; //reduce the number of links remaining\n                    if (remaining_links[target_node.id] == 0) {\n                        S.push(target_node);\n                    } //if no more links, then add to starters array\n                }\n            }\n        }\n\n        //the remaining ones (loops)\n        for (var i in M) {\n            L.push(M[i]);\n        }\n\n        if (L.length != this._nodes.length && LiteGraph.debug) {\n            console.warn(\"something went wrong, nodes missing\");\n        }\n\n        var l = L.length;\n\n        //save order number in the node\n        for (var i = 0; i < l; ++i) {\n            L[i].order = i;\n        }\n\n        //sort now by priority\n        L = L.sort(function(A, B) {\n            var Ap = A.constructor.priority || A.priority || 0;\n            var Bp = B.constructor.priority || B.priority || 0;\n            if (Ap == Bp) {\n                //if same priority, sort by order\n                return A.order - B.order;\n            }\n            return Ap - Bp; //sort by priority\n        });\n\n        //save order number in the node, again...\n        for (var i = 0; i < l; ++i) {\n            L[i].order = i;\n        }\n\n        return L;\n    };\n\n    /**\n     * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.\n     * It doesn't include the node itself\n     * @method getAncestors\n     * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution\n     */\n    LGraph.prototype.getAncestors = function(node) {\n        var ancestors = [];\n        var pending = [node];\n        var visited = {};\n\n        while (pending.length) {\n            var current = pending.shift();\n            if (!current.inputs) {\n                continue;\n            }\n            if (!visited[current.id] && current != node) {\n                visited[current.id] = true;\n                ancestors.push(current);\n            }\n\n            for (var i = 0; i < current.inputs.length; ++i) {\n                var input = current.getInputNode(i);\n                if (input && ancestors.indexOf(input) == -1) {\n                    pending.push(input);\n                }\n            }\n        }\n\n        ancestors.sort(function(a, b) {\n            return a.order - b.order;\n        });\n        return ancestors;\n    };\n\n    /**\n     * Positions every node in a more readable manner\n     * @method arrange\n     */\n    LGraph.prototype.arrange = function (margin, layout) {\n        margin = margin || 100;\n\n        const nodes = this.computeExecutionOrder(false, true);\n        const columns = [];\n        for (let i = 0; i < nodes.length; ++i) {\n            const node = nodes[i];\n            const col = node._level || 1;\n            if (!columns[col]) {\n                columns[col] = [];\n            }\n            columns[col].push(node);\n        }\n\n        let x = margin;\n\n        for (let i = 0; i < columns.length; ++i) {\n            const column = columns[i];\n            if (!column) {\n                continue;\n            }\n            let max_size = 100;\n            let y = margin + LiteGraph.NODE_TITLE_HEIGHT;\n            for (let j = 0; j < column.length; ++j) {\n                const node = column[j];\n                node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x;\n                node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y;\n                const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0;\n                if (node.size[max_size_index] > max_size) {\n                    max_size = node.size[max_size_index];\n                }\n                const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1;\n                y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT;\n            }\n            x += max_size + margin;\n        }\n\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * Returns the amount of time the graph has been running in milliseconds\n     * @method getTime\n     * @return {number} number of milliseconds the graph has been running\n     */\n    LGraph.prototype.getTime = function() {\n        return this.globaltime;\n    };\n\n    /**\n     * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant\n     * @method getFixedTime\n     * @return {number} number of milliseconds the graph has been running\n     */\n\n    LGraph.prototype.getFixedTime = function() {\n        return this.fixedtime;\n    };\n\n    /**\n     * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct\n     * if the nodes are using graphical actions\n     * @method getElapsedTime\n     * @return {number} number of milliseconds it took the last cycle\n     */\n\n    LGraph.prototype.getElapsedTime = function() {\n        return this.elapsed_time;\n    };\n\n    /**\n     * Sends an event to all the nodes, useful to trigger stuff\n     * @method sendEventToAllNodes\n     * @param {String} eventname the name of the event (function to be called)\n     * @param {Array} params parameters in array format\n     */\n    LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) {\n        mode = mode || LiteGraph.ALWAYS;\n\n        var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes;\n        if (!nodes) {\n            return;\n        }\n\n        for (var j = 0, l = nodes.length; j < l; ++j) {\n            var node = nodes[j];\n\n            if (\n                node.constructor === LiteGraph.Subgraph &&\n                eventname != \"onExecute\"\n            ) {\n                if (node.mode == mode) {\n                    node.sendEventToAllNodes(eventname, params, mode);\n                }\n                continue;\n            }\n\n            if (!node[eventname] || node.mode != mode) {\n                continue;\n            }\n            if (params === undefined) {\n                node[eventname]();\n            } else if (params && params.constructor === Array) {\n                node[eventname].apply(node, params);\n            } else {\n                node[eventname](params);\n            }\n        }\n    };\n\n    LGraph.prototype.sendActionToCanvas = function(action, params) {\n        if (!this.list_of_graphcanvas) {\n            return;\n        }\n\n        for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n            var c = this.list_of_graphcanvas[i];\n            if (c[action]) {\n                c[action].apply(c, params);\n            }\n        }\n    };\n\n    /**\n     * Adds a new node instance to this graph\n     * @method add\n     * @param {LGraphNode} node the instance of the node\n     */\n\n    LGraph.prototype.add = function(node, skip_compute_order) {\n        if (!node) {\n            return;\n        }\n\n        //groups\n        if (node.constructor === LGraphGroup) {\n            this._groups.push(node);\n            this.setDirtyCanvas(true);\n            this.change();\n            node.graph = this;\n            this._version++;\n            return;\n        }\n\n        //nodes\n        if (node.id != -1 && this._nodes_by_id[node.id] != null) {\n            console.warn(\n                \"LiteGraph: there is already a node with this ID, changing it\"\n            );\n            if (LiteGraph.use_uuids) {\n                node.id = LiteGraph.uuidv4();\n            }\n            else {\n                node.id = ++this.last_node_id;\n            }\n        }\n\n        if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) {\n            throw \"LiteGraph: max number of nodes in a graph reached\";\n        }\n\n        //give him an id\n        if (LiteGraph.use_uuids) {\n            if (node.id == null || node.id == -1)\n                node.id = LiteGraph.uuidv4();\n        }\n        else {\n            if (node.id == null || node.id == -1) {\n                node.id = ++this.last_node_id;\n            } else if (this.last_node_id < node.id) {\n                this.last_node_id = node.id;\n            }\n        }\n\n        node.graph = this;\n        this._version++;\n\n        this._nodes.push(node);\n        this._nodes_by_id[node.id] = node;\n\n        if (node.onAdded) {\n            node.onAdded(this);\n        }\n\n        if (this.config.align_to_grid) {\n            node.alignToGrid();\n        }\n\n        if (!skip_compute_order) {\n            this.updateExecutionOrder();\n        }\n\n        if (this.onNodeAdded) {\n            this.onNodeAdded(node);\n        }\n\n        this.setDirtyCanvas(true);\n        this.change();\n\n        return node; //to chain actions\n    };\n\n    /**\n     * Removes a node from the graph\n     * @method remove\n     * @param {LGraphNode} node the instance of the node\n     */\n\n    LGraph.prototype.remove = function(node) {\n        if (node.constructor === LiteGraph.LGraphGroup) {\n            var index = this._groups.indexOf(node);\n            if (index != -1) {\n                this._groups.splice(index, 1);\n            }\n            node.graph = null;\n            this._version++;\n            this.setDirtyCanvas(true, true);\n            this.change();\n            return;\n        }\n\n        if (this._nodes_by_id[node.id] == null) {\n            return;\n        } //not found\n\n        if (node.ignore_remove) {\n            return;\n        } //cannot be removed\n\n\t\tthis.beforeChange(); //sure? - almost sure is wrong\n\n        //disconnect inputs\n        if (node.inputs) {\n            for (var i = 0; i < node.inputs.length; i++) {\n                var slot = node.inputs[i];\n                if (slot.link != null) {\n                    node.disconnectInput(i);\n                }\n            }\n        }\n\n        //disconnect outputs\n        if (node.outputs) {\n            for (var i = 0; i < node.outputs.length; i++) {\n                var slot = node.outputs[i];\n                if (slot.links != null && slot.links.length) {\n                    node.disconnectOutput(i);\n                }\n            }\n        }\n\n        //node.id = -1; //why?\n\n        //callback\n        if (node.onRemoved) {\n            node.onRemoved();\n        }\n\n        node.graph = null;\n        this._version++;\n\n        //remove from canvas render\n        if (this.list_of_graphcanvas) {\n            for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n                var canvas = this.list_of_graphcanvas[i];\n                if (canvas.selected_nodes[node.id]) {\n                    delete canvas.selected_nodes[node.id];\n                }\n                if (canvas.node_dragged == node) {\n                    canvas.node_dragged = null;\n                }\n            }\n        }\n\n        //remove from containers\n        var pos = this._nodes.indexOf(node);\n        if (pos != -1) {\n            this._nodes.splice(pos, 1);\n        }\n        delete this._nodes_by_id[node.id];\n\n        if (this.onNodeRemoved) {\n            this.onNodeRemoved(node);\n        }\n\n\t\t//close panels\n\t\tthis.sendActionToCanvas(\"checkPanels\");\n\n        this.setDirtyCanvas(true, true);\n\t\tthis.afterChange(); //sure? - almost sure is wrong\n        this.change();\n\n        this.updateExecutionOrder();\n    };\n\n    /**\n     * Returns a node by its id.\n     * @method getNodeById\n     * @param {Number} id\n     */\n\n    LGraph.prototype.getNodeById = function(id) {\n        if (id == null) {\n            return null;\n        }\n        return this._nodes_by_id[id];\n    };\n\n    /**\n     * Returns a list of nodes that matches a class\n     * @method findNodesByClass\n     * @param {Class} classObject the class itself (not an string)\n     * @return {Array} a list with all the nodes of this type\n     */\n    LGraph.prototype.findNodesByClass = function(classObject, result) {\n        result = result || [];\n        result.length = 0;\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].constructor === classObject) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns a list of nodes that matches a type\n     * @method findNodesByType\n     * @param {String} type the name of the node type\n     * @return {Array} a list with all the nodes of this type\n     */\n    LGraph.prototype.findNodesByType = function(type, result) {\n        var type = type.toLowerCase();\n        result = result || [];\n        result.length = 0;\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].type.toLowerCase() == type) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns the first node that matches a name in its title\n     * @method findNodeByTitle\n     * @param {String} name the name of the node to search\n     * @return {Node} the node or null\n     */\n    LGraph.prototype.findNodeByTitle = function(title) {\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].title == title) {\n                return this._nodes[i];\n            }\n        }\n        return null;\n    };\n\n    /**\n     * Returns a list of nodes that matches a name\n     * @method findNodesByTitle\n     * @param {String} name the name of the node to search\n     * @return {Array} a list with all the nodes with this name\n     */\n    LGraph.prototype.findNodesByTitle = function(title) {\n        var result = [];\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].title == title) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns the top-most node in this position of the canvas\n     * @method getNodeOnPos\n     * @param {number} x the x coordinate in canvas space\n     * @param {number} y the y coordinate in canvas space\n     * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph\n     * @return {LGraphNode} the node at this position or null\n     */\n    LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) {\n        nodes_list = nodes_list || this._nodes;\n\t\tvar nRet = null;\n        for (var i = nodes_list.length - 1; i >= 0; i--) {\n            var n = nodes_list[i];\n            if (n.isPointInside(x, y, margin)) {\n                // check for lesser interest nodes (TODO check for overlapping, use the top)\n\t\t\t\t/*if (typeof n == \"LGraphGroup\"){\n\t\t\t\t\tnRet = n;\n\t\t\t\t}else{*/\n\t\t\t\t\treturn n;\n\t\t\t\t/*}*/\n            }\n        }\n        return nRet;\n    };\n\n    /**\n     * Returns the top-most group in that position\n     * @method getGroupOnPos\n     * @param {number} x the x coordinate in canvas space\n     * @param {number} y the y coordinate in canvas space\n     * @return {LGraphGroup} the group or null\n     */\n    LGraph.prototype.getGroupOnPos = function(x, y) {\n        for (var i = this._groups.length - 1; i >= 0; i--) {\n            var g = this._groups[i];\n            if (g.isPointInside(x, y, 2, true)) {\n                return g;\n            }\n        }\n        return null;\n    };\n\n    /**\n     * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution\n     * this replaces the ones using the old version with the new version\n     * @method checkNodeTypes\n     */\n    LGraph.prototype.checkNodeTypes = function() {\n        var changes = false;\n        for (var i = 0; i < this._nodes.length; i++) {\n            var node = this._nodes[i];\n            var ctor = LiteGraph.registered_node_types[node.type];\n            if (node.constructor == ctor) {\n                continue;\n            }\n            console.log(\"node being replaced by newer version: \" + node.type);\n            var newnode = LiteGraph.createNode(node.type);\n            changes = true;\n            this._nodes[i] = newnode;\n            newnode.configure(node.serialize());\n            newnode.graph = this;\n            this._nodes_by_id[newnode.id] = newnode;\n            if (node.inputs) {\n                newnode.inputs = node.inputs.concat();\n            }\n            if (node.outputs) {\n                newnode.outputs = node.outputs.concat();\n            }\n        }\n        this.updateExecutionOrder();\n    };\n\n    // ********** GLOBALS *****************\n\n    LGraph.prototype.onAction = function(action, param, options) {\n        this._input_nodes = this.findNodesByClass(\n            LiteGraph.GraphInput,\n            this._input_nodes\n        );\n        for (var i = 0; i < this._input_nodes.length; ++i) {\n            var node = this._input_nodes[i];\n            if (node.properties.name != action) {\n                continue;\n            }\n            //wrap node.onAction(action, param);\n            node.actionDo(action, param, options);\n            break;\n        }\n    };\n\n    LGraph.prototype.trigger = function(action, param) {\n        if (this.onTrigger) {\n            this.onTrigger(action, param);\n        }\n    };\n\n    /**\n     * Tell this graph it has a global graph input of this type\n     * @method addGlobalInput\n     * @param {String} name\n     * @param {String} type\n     * @param {*} value [optional]\n     */\n    LGraph.prototype.addInput = function(name, type, value) {\n        var input = this.inputs[name];\n        if (input) {\n            //already exist\n            return;\n        }\n\n\t\tthis.beforeChange();\n        this.inputs[name] = { name: name, type: type, value: value };\n        this._version++;\n\t\tthis.afterChange();\n\n        if (this.onInputAdded) {\n            this.onInputAdded(name, type);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Assign a data to the global graph input\n     * @method setGlobalInputData\n     * @param {String} name\n     * @param {*} data\n     */\n    LGraph.prototype.setInputData = function(name, data) {\n        var input = this.inputs[name];\n        if (!input) {\n            return;\n        }\n        input.value = data;\n    };\n\n    /**\n     * Returns the current value of a global graph input\n     * @method getInputData\n     * @param {String} name\n     * @return {*} the data\n     */\n    LGraph.prototype.getInputData = function(name) {\n        var input = this.inputs[name];\n        if (!input) {\n            return null;\n        }\n        return input.value;\n    };\n\n    /**\n     * Changes the name of a global graph input\n     * @method renameInput\n     * @param {String} old_name\n     * @param {String} new_name\n     */\n    LGraph.prototype.renameInput = function(old_name, name) {\n        if (name == old_name) {\n            return;\n        }\n\n        if (!this.inputs[old_name]) {\n            return false;\n        }\n\n        if (this.inputs[name]) {\n            console.error(\"there is already one input with that name\");\n            return false;\n        }\n\n        this.inputs[name] = this.inputs[old_name];\n        delete this.inputs[old_name];\n        this._version++;\n\n        if (this.onInputRenamed) {\n            this.onInputRenamed(old_name, name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Changes the type of a global graph input\n     * @method changeInputType\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.changeInputType = function(name, type) {\n        if (!this.inputs[name]) {\n            return false;\n        }\n\n        if (\n            this.inputs[name].type &&\n            String(this.inputs[name].type).toLowerCase() ==\n                String(type).toLowerCase()\n        ) {\n            return;\n        }\n\n        this.inputs[name].type = type;\n        this._version++;\n        if (this.onInputTypeChanged) {\n            this.onInputTypeChanged(name, type);\n        }\n    };\n\n    /**\n     * Removes a global graph input\n     * @method removeInput\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.removeInput = function(name) {\n        if (!this.inputs[name]) {\n            return false;\n        }\n\n        delete this.inputs[name];\n        this._version++;\n\n        if (this.onInputRemoved) {\n            this.onInputRemoved(name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n        return true;\n    };\n\n    /**\n     * Creates a global graph output\n     * @method addOutput\n     * @param {String} name\n     * @param {String} type\n     * @param {*} value\n     */\n    LGraph.prototype.addOutput = function(name, type, value) {\n        this.outputs[name] = { name: name, type: type, value: value };\n        this._version++;\n\n        if (this.onOutputAdded) {\n            this.onOutputAdded(name, type);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Assign a data to the global output\n     * @method setOutputData\n     * @param {String} name\n     * @param {String} value\n     */\n    LGraph.prototype.setOutputData = function(name, value) {\n        var output = this.outputs[name];\n        if (!output) {\n            return;\n        }\n        output.value = value;\n    };\n\n    /**\n     * Returns the current value of a global graph output\n     * @method getOutputData\n     * @param {String} name\n     * @return {*} the data\n     */\n    LGraph.prototype.getOutputData = function(name) {\n        var output = this.outputs[name];\n        if (!output) {\n            return null;\n        }\n        return output.value;\n    };\n\n    /**\n     * Renames a global graph output\n     * @method renameOutput\n     * @param {String} old_name\n     * @param {String} new_name\n     */\n    LGraph.prototype.renameOutput = function(old_name, name) {\n        if (!this.outputs[old_name]) {\n            return false;\n        }\n\n        if (this.outputs[name]) {\n            console.error(\"there is already one output with that name\");\n            return false;\n        }\n\n        this.outputs[name] = this.outputs[old_name];\n        delete this.outputs[old_name];\n        this._version++;\n\n        if (this.onOutputRenamed) {\n            this.onOutputRenamed(old_name, name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Changes the type of a global graph output\n     * @method changeOutputType\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.changeOutputType = function(name, type) {\n        if (!this.outputs[name]) {\n            return false;\n        }\n\n        if (\n            this.outputs[name].type &&\n            String(this.outputs[name].type).toLowerCase() ==\n                String(type).toLowerCase()\n        ) {\n            return;\n        }\n\n        this.outputs[name].type = type;\n        this._version++;\n        if (this.onOutputTypeChanged) {\n            this.onOutputTypeChanged(name, type);\n        }\n    };\n\n    /**\n     * Removes a global graph output\n     * @method removeOutput\n     * @param {String} name\n     */\n    LGraph.prototype.removeOutput = function(name) {\n        if (!this.outputs[name]) {\n            return false;\n        }\n        delete this.outputs[name];\n        this._version++;\n\n        if (this.onOutputRemoved) {\n            this.onOutputRemoved(name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n        return true;\n    };\n\n    LGraph.prototype.triggerInput = function(name, value) {\n        var nodes = this.findNodesByTitle(name);\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].onTrigger(value);\n        }\n    };\n\n    LGraph.prototype.setCallback = function(name, func) {\n        var nodes = this.findNodesByTitle(name);\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].setTrigger(func);\n        }\n    };\n\n\t//used for undo, called before any change is made to the graph\n    LGraph.prototype.beforeChange = function(info) {\n        if (this.onBeforeChange) {\n            this.onBeforeChange(this,info);\n        }\n        this.sendActionToCanvas(\"onBeforeChange\", this);\n    };\n\n\t//used to resend actions, called after any change is made to the graph\n    LGraph.prototype.afterChange = function(info) {\n        if (this.onAfterChange) {\n            this.onAfterChange(this,info);\n        }\n        this.sendActionToCanvas(\"onAfterChange\", this);\n    };\n\n    LGraph.prototype.connectionChange = function(node, link_info) {\n        this.updateExecutionOrder();\n        if (this.onConnectionChange) {\n            this.onConnectionChange(node);\n        }\n        this._version++;\n        this.sendActionToCanvas(\"onConnectionChange\");\n    };\n\n    /**\n     * returns if the graph is in live mode\n     * @method isLive\n     */\n\n    LGraph.prototype.isLive = function() {\n        if (!this.list_of_graphcanvas) {\n            return false;\n        }\n\n        for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n            var c = this.list_of_graphcanvas[i];\n            if (c.live_mode) {\n                return true;\n            }\n        }\n        return false;\n    };\n\n    /**\n     * clears the triggered slot animation in all links (stop visual animation)\n     * @method clearTriggeredSlots\n     */\n    LGraph.prototype.clearTriggeredSlots = function() {\n        for (var i in this.links) {\n            var link_info = this.links[i];\n            if (!link_info) {\n                continue;\n            }\n            if (link_info._last_time) {\n                link_info._last_time = 0;\n            }\n        }\n    };\n\n    /* Called when something visually changed (not the graph!) */\n    LGraph.prototype.change = function() {\n        if (LiteGraph.debug) {\n            console.log(\"Graph changed\");\n        }\n        this.sendActionToCanvas(\"setDirty\", [true, true]);\n        if (this.on_change) {\n            this.on_change(this);\n        }\n    };\n\n    LGraph.prototype.setDirtyCanvas = function(fg, bg) {\n        this.sendActionToCanvas(\"setDirty\", [fg, bg]);\n    };\n\n    /**\n     * Destroys a link\n     * @method removeLink\n     * @param {Number} link_id\n     */\n    LGraph.prototype.removeLink = function(link_id) {\n        var link = this.links[link_id];\n        if (!link) {\n            return;\n        }\n        var node = this.getNodeById(link.target_id);\n        if (node) {\n            node.disconnectInput(link.target_slot);\n        }\n    };\n\n    //save and recover app state ***************************************\n    /**\n     * Creates a Object containing all the info about this graph, it can be serialized\n     * @method serialize\n     * @return {Object} value of the node\n     */\n    LGraph.prototype.serialize = function() {\n        var nodes_info = [];\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            nodes_info.push(this._nodes[i].serialize());\n        }\n\n        //pack link info into a non-verbose format\n        var links = [];\n        for (var i in this.links) {\n            //links is an OBJECT\n            var link = this.links[i];\n            if (!link.serialize) {\n                //weird bug I havent solved yet\n                console.warn(\n                    \"weird LLink bug, link info is not a LLink but a regular object\"\n                );\n                var link2 = new LLink();\n                for (var j in link) { \n                    link2[j] = link[j];\n                }\n                this.links[i] = link2;\n                link = link2;\n            }\n\n            links.push(link.serialize());\n        }\n\n        var groups_info = [];\n        for (var i = 0; i < this._groups.length; ++i) {\n            groups_info.push(this._groups[i].serialize());\n        }\n\n        var data = {\n            last_node_id: this.last_node_id,\n            last_link_id: this.last_link_id,\n            nodes: nodes_info,\n            links: links,\n            groups: groups_info,\n            config: this.config,\n\t\t\textra: this.extra,\n            version: LiteGraph.VERSION\n        };\n\n\t\tif(this.onSerialize)\n\t\t\tthis.onSerialize(data);\n\n        return data;\n    };\n\n    /**\n     * Configure a graph from a JSON string\n     * @method configure\n     * @param {String} str configure a graph from a JSON string\n     * @param {Boolean} returns if there was any error parsing\n     */\n    LGraph.prototype.configure = function(data, keep_old) {\n        if (!data) {\n            return;\n        }\n\n        if (!keep_old) {\n            this.clear();\n        }\n\n        var nodes = data.nodes;\n\n        //decode links info (they are very verbose)\n        if (data.links && data.links.constructor === Array) {\n            var links = [];\n            for (var i = 0; i < data.links.length; ++i) {\n                var link_data = data.links[i];\n\t\t\t\tif(!link_data) //weird bug\n\t\t\t\t{\n\t\t\t\t\tconsole.warn(\"serialized graph link data contains errors, skipping.\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n                var link = new LLink();\n                link.configure(link_data);\n                links[link.id] = link;\n            }\n            data.links = links;\n        }\n\n        //copy all stored fields\n        for (var i in data) {\n\t\t\tif(i == \"nodes\" || i == \"groups\" ) //links must be accepted\n\t\t\t\tcontinue;\n            this[i] = data[i];\n        }\n\n        var error = false;\n\n        //create nodes\n        this._nodes = [];\n        if (nodes) {\n            for (var i = 0, l = nodes.length; i < l; ++i) {\n                var n_info = nodes[i]; //stored info\n                var node = LiteGraph.createNode(n_info.type, n_info.title);\n                if (!node) {\n                    if (LiteGraph.debug) {\n                        console.log(\n                            \"Node not found or has errors: \" + n_info.type\n                        );\n                    }\n\n                    //in case of error we create a replacement node to avoid losing info\n                    node = new LGraphNode();\n                    node.last_serialization = n_info;\n                    node.has_errors = true;\n                    error = true;\n                    //continue;\n                }\n\n                node.id = n_info.id; //id it or it will create a new id\n                this.add(node, true); //add before configure, otherwise configure cannot create links\n            }\n\n            //configure nodes afterwards so they can reach each other\n            for (var i = 0, l = nodes.length; i < l; ++i) {\n                var n_info = nodes[i];\n                var node = this.getNodeById(n_info.id);\n                if (node) {\n                    node.configure(n_info);\n                }\n            }\n        }\n\n        //groups\n        this._groups.length = 0;\n        if (data.groups) {\n            for (var i = 0; i < data.groups.length; ++i) {\n                var group = new LiteGraph.LGraphGroup();\n                group.configure(data.groups[i]);\n                this.add(group);\n            }\n        }\n\n        this.updateExecutionOrder();\n\n\t\tthis.extra = data.extra || {};\n\n\t\tif(this.onConfigure)\n\t\t\tthis.onConfigure(data);\n\n        this._version++;\n        this.setDirtyCanvas(true, true);\n        return error;\n    };\n\n    LGraph.prototype.load = function(url, callback) {\n        var that = this;\n\n\t\t//from file\n\t\tif(url.constructor === File || url.constructor === Blob)\n\t\t{\n\t\t\tvar reader = new FileReader();\n\t\t\treader.addEventListener('load', function(event) {\n\t\t\t\tvar data = JSON.parse(event.target.result);\n\t\t\t\tthat.configure(data);\n\t\t\t\tif(callback)\n\t\t\t\t\tcallback();\n\t\t\t});\n\t\t\t\n\t\t\treader.readAsText(url);\n\t\t\treturn;\n\t\t}\n\n\t\t//is a string, then an URL\n        var req = new XMLHttpRequest();\n        req.open(\"GET\", url, true);\n        req.send(null);\n        req.onload = function(oEvent) {\n            if (req.status !== 200) {\n                console.error(\"Error loading graph:\", req.status, req.response);\n                return;\n            }\n            var data = JSON.parse( req.response );\n            that.configure(data);\n\t\t\tif(callback)\n\t\t\t\tcallback();\n        };\n        req.onerror = function(err) {\n            console.error(\"Error loading graph:\", err);\n        };\n    };\n\n    LGraph.prototype.onNodeTrace = function(node, msg, color) {\n        //TODO\n    };\n\n    //this is the class in charge of storing link information\n    function LLink(id, type, origin_id, origin_slot, target_id, target_slot) {\n        this.id = id;\n        this.type = type;\n        this.origin_id = origin_id;\n        this.origin_slot = origin_slot;\n        this.target_id = target_id;\n        this.target_slot = target_slot;\n\n        this._data = null;\n        this._pos = new Float32Array(2); //center\n    }\n\n    LLink.prototype.configure = function(o) {\n        if (o.constructor === Array) {\n            this.id = o[0];\n            this.origin_id = o[1];\n            this.origin_slot = o[2];\n            this.target_id = o[3];\n            this.target_slot = o[4];\n            this.type = o[5];\n        } else {\n            this.id = o.id;\n            this.type = o.type;\n            this.origin_id = o.origin_id;\n            this.origin_slot = o.origin_slot;\n            this.target_id = o.target_id;\n            this.target_slot = o.target_slot;\n        }\n    };\n\n    LLink.prototype.serialize = function() {\n        return [\n            this.id,\n            this.origin_id,\n            this.origin_slot,\n            this.target_id,\n            this.target_slot,\n            this.type\n        ];\n    };\n\n    LiteGraph.LLink = LLink;\n\n    // *************************************************************\n    //   Node CLASS                                          *******\n    // *************************************************************\n\n    /*\n\ttitle: string\n\tpos: [x,y]\n\tsize: [x,y]\n\n\tinput|output: every connection\n\t\t+  { name:string, type:string, pos: [x,y]=Optional, direction: \"input\"|\"output\", links: Array });\n\n\tgeneral properties:\n\t\t+ clip_area: if you render outside the node, it will be clipped\n\t\t+ unsafe_execution: not allowed for safe execution\n\t\t+ skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected\n\t\t+ resizable: if set to false it wont be resizable with the mouse\n\t\t+ horizontal: slots are distributed horizontally\n\t\t+ widgets_start_y: widgets start at y distance from the top of the node\n\t\n\tflags object:\n\t\t+ collapsed: if it is collapsed\n\n\tsupported callbacks:\n\t\t+ onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading)\n\t\t+ onRemoved: when removed from graph\n\t\t+ onStart:\twhen the graph starts playing\n\t\t+ onStop:\twhen the graph stops playing\n\t\t+ onDrawForeground: render the inside widgets inside the node\n\t\t+ onDrawBackground: render the background area inside the node (only in edit mode)\n\t\t+ onMouseDown\n\t\t+ onMouseMove\n\t\t+ onMouseUp\n\t\t+ onMouseEnter\n\t\t+ onMouseLeave\n\t\t+ onExecute: execute the node\n\t\t+ onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour)\n\t\t+ onGetInputs: returns an array of possible inputs\n\t\t+ onGetOutputs: returns an array of possible outputs\n\t\t+ onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h])\n\t\t+ onDblClick: double clicked in the node\n\t\t+ onInputDblClick: input slot double clicked (can be used to automatically create a node connected)\n\t\t+ onOutputDblClick: output slot double clicked (can be used to automatically create a node connected)\n\t\t+ onConfigure: called after the node has been configured\n\t\t+ onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data)\n\t\t+ onSelected\n\t\t+ onDeselected\n\t\t+ onDropItem : DOM item dropped over the node\n\t\t+ onDropFile : file dropped over the node\n\t\t+ onConnectInput : if returns false the incoming connection will be canceled\n\t\t+ onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info )\n\t\t+ onAction: action slot triggered\n\t\t+ getExtraMenuOptions: to add option to context menu\n*/\n\n    /**\n     * Base Class for all the node type classes\n     * @class LGraphNode\n     * @param {String} name a name for the node\n     */\n\n    function LGraphNode(title) {\n        this._ctor(title);\n    }\n\n    global.LGraphNode = LiteGraph.LGraphNode = LGraphNode;\n\n    LGraphNode.prototype._ctor = function(title) {\n        this.title = title || \"Unnamed\";\n        this.size = [LiteGraph.NODE_WIDTH, 60];\n        this.graph = null;\n\n        this._pos = new Float32Array(10, 10);\n\n        Object.defineProperty(this, \"pos\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._pos[0] = v[0];\n                this._pos[1] = v[1];\n            },\n            get: function() {\n                return this._pos;\n            },\n            enumerable: true\n        });\n\n        if (LiteGraph.use_uuids) {\n            this.id = LiteGraph.uuidv4();\n        }\n        else {\n            this.id = -1; //not know till not added\n        }\n        this.type = null;\n\n        //inputs available: array of inputs\n        this.inputs = [];\n        this.outputs = [];\n        this.connections = [];\n\n        //local data\n        this.properties = {}; //for the values\n        this.properties_info = []; //for the info\n\n        this.flags = {};\n    };\n\n    /**\n     * configure a node from an object containing the serialized info\n     * @method configure\n     */\n    LGraphNode.prototype.configure = function(info) {\n        if (this.graph) {\n            this.graph._version++;\n        }\n        for (var j in info) {\n            if (j == \"properties\") {\n                //i don't want to clone properties, I want to reuse the old container\n                for (var k in info.properties) {\n                    this.properties[k] = info.properties[k];\n                    if (this.onPropertyChanged) {\n                        this.onPropertyChanged( k, info.properties[k] );\n                    }\n                }\n                continue;\n            }\n\n            if (info[j] == null) {\n                continue;\n            } else if (typeof info[j] == \"object\") {\n                //object\n                if (this[j] && this[j].configure) {\n                    this[j].configure(info[j]);\n                } else {\n                    this[j] = LiteGraph.cloneObject(info[j], this[j]);\n                }\n            } //value\n            else {\n                this[j] = info[j];\n            }\n        }\n\n        if (!info.title) {\n            this.title = this.constructor.title;\n        }\n\n\t\tif (this.inputs) {\n\t\t\tfor (var i = 0; i < this.inputs.length; ++i) {\n\t\t\t\tvar input = this.inputs[i];\n\t\t\t\tvar link_info = this.graph ? this.graph.links[input.link] : null;\n\t\t\t\tif (this.onConnectionsChange)\n\t\t\t\t\tthis.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated\n\n\t\t\t\tif( this.onInputAdded )\n\t\t\t\t\tthis.onInputAdded(input);\n\n\t\t\t}\n\t\t}\n\n\t\tif (this.outputs) {\n\t\t\tfor (var i = 0; i < this.outputs.length; ++i) {\n\t\t\t\tvar output = this.outputs[i];\n\t\t\t\tif (!output.links) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfor (var j = 0; j < output.links.length; ++j) {\n\t\t\t\t\tvar link_info = this.graph \t? this.graph.links[output.links[j]] : null;\n\t\t\t\t\tif (this.onConnectionsChange)\n\t\t\t\t\t\tthis.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated\n\t\t\t\t}\n\n\t\t\t\tif( this.onOutputAdded )\n\t\t\t\t\tthis.onOutputAdded(output);\n\t\t\t}\n        }\n\n\t\tif( this.widgets )\n\t\t{\n\t\t\tfor (var i = 0; i < this.widgets.length; ++i)\n\t\t\t{\n\t\t\t\tvar w = this.widgets[i];\n\t\t\t\tif(!w)\n\t\t\t\t\tcontinue;\n\t\t\t\tif(w.options && w.options.property && (this.properties[ w.options.property ] != undefined))\n\t\t\t\t\tw.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) );\n\t\t\t}\n\t\t\tif (info.widgets_values) {\n\t\t\t\tfor (var i = 0; i < info.widgets_values.length; ++i) {\n\t\t\t\t\tif (this.widgets[i]) {\n\t\t\t\t\t\tthis.widgets[i].value = info.widgets_values[i];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n        if (this.onConfigure) {\n            this.onConfigure(info);\n        }\n    };\n\n    /**\n     * serialize the content\n     * @method serialize\n     */\n\n    LGraphNode.prototype.serialize = function() {\n        //create serialization object\n        var o = {\n            id: this.id,\n            type: this.type,\n            pos: this.pos,\n            size: this.size,\n            flags: LiteGraph.cloneObject(this.flags),\n\t\t\torder: this.order,\n            mode: this.mode\n        };\n\n        //special case for when there were errors\n        if (this.constructor === LGraphNode && this.last_serialization) {\n            return this.last_serialization;\n        }\n\n        if (this.inputs) {\n            o.inputs = this.inputs;\n        }\n\n        if (this.outputs) {\n            //clear outputs last data (because data in connections is never serialized but stored inside the outputs info)\n            for (var i = 0; i < this.outputs.length; i++) {\n                delete this.outputs[i]._data;\n            }\n            o.outputs = this.outputs;\n        }\n\n        if (this.title && this.title != this.constructor.title) {\n            o.title = this.title;\n        }\n\n        if (this.properties) {\n            o.properties = LiteGraph.cloneObject(this.properties);\n        }\n\n        if (this.widgets && this.serialize_widgets) {\n            o.widgets_values = [];\n            for (var i = 0; i < this.widgets.length; ++i) {\n\t\t\t\tif(this.widgets[i])\n\t                o.widgets_values[i] = this.widgets[i].value;\n\t\t\t\telse\n\t\t\t\t\to.widgets_values[i] = null;\n            }\n        }\n\n        if (!o.type) {\n            o.type = this.constructor.type;\n        }\n\n        if (this.color) {\n            o.color = this.color;\n        }\n        if (this.bgcolor) {\n            o.bgcolor = this.bgcolor;\n        }\n        if (this.boxcolor) {\n            o.boxcolor = this.boxcolor;\n        }\n        if (this.shape) {\n            o.shape = this.shape;\n        }\n\n        if (this.onSerialize) {\n            if (this.onSerialize(o)) {\n                console.warn(\n                    \"node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter\"\n                );\n            }\n        }\n\n        return o;\n    };\n\n    /* Creates a clone of this node */\n    LGraphNode.prototype.clone = function() {\n        var node = LiteGraph.createNode(this.type);\n        if (!node) {\n            return null;\n        }\n\n        //we clone it because serialize returns shared containers\n        var data = LiteGraph.cloneObject(this.serialize());\n\n        //remove links\n        if (data.inputs) {\n            for (var i = 0; i < data.inputs.length; ++i) {\n                data.inputs[i].link = null;\n            }\n        }\n\n        if (data.outputs) {\n            for (var i = 0; i < data.outputs.length; ++i) {\n                if (data.outputs[i].links) {\n                    data.outputs[i].links.length = 0;\n                }\n            }\n        }\n\n        delete data[\"id\"];\n\n        if (LiteGraph.use_uuids) {\n            data[\"id\"] = LiteGraph.uuidv4()\n        }\n\n        //remove links\n        node.configure(data);\n\n        return node;\n    };\n\n    /**\n     * serialize and stringify\n     * @method toString\n     */\n\n    LGraphNode.prototype.toString = function() {\n        return JSON.stringify(this.serialize());\n    };\n    //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph\n\n    /**\n     * get the title string\n     * @method getTitle\n     */\n\n    LGraphNode.prototype.getTitle = function() {\n        return this.title || this.constructor.title;\n    };\n\n    /**\n     * sets the value of a property\n     * @method setProperty\n     * @param {String} name\n     * @param {*} value\n     */\n    LGraphNode.prototype.setProperty = function(name, value) {\n        if (!this.properties) {\n            this.properties = {};\n        }\n\t\tif( value === this.properties[name] )\n\t\t\treturn;\n\t\tvar prev_value = this.properties[name];\n        this.properties[name] = value;\n        if (this.onPropertyChanged) {\n            if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change\n\t\t\t\tthis.properties[name] = prev_value;\n        }\n\t\tif(this.widgets) //widgets could be linked to properties\n\t\t\tfor(var i = 0; i < this.widgets.length; ++i)\n\t\t\t{\n\t\t\t\tvar w = this.widgets[i];\n\t\t\t\tif(!w)\n\t\t\t\t\tcontinue;\n\t\t\t\tif(w.options.property == name)\n\t\t\t\t{\n\t\t\t\t\tw.value = value;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n    };\n\n    // Execution *************************\n    /**\n     * sets the output data\n     * @method setOutputData\n     * @param {number} slot\n     * @param {*} data\n     */\n    LGraphNode.prototype.setOutputData = function(slot, data) {\n        if (!this.outputs) {\n            return;\n        }\n\n        //this maybe slow and a niche case\n        //if(slot && slot.constructor === String)\n        //\tslot = this.findOutputSlot(slot);\n\n        if (slot == -1 || slot >= this.outputs.length) {\n            return;\n        }\n\n        var output_info = this.outputs[slot];\n        if (!output_info) {\n            return;\n        }\n\n        //store data in the output itself in case we want to debug\n        output_info._data = data;\n\n        //if there are connections, pass the data to the connections\n        if (this.outputs[slot].links) {\n            for (var i = 0; i < this.outputs[slot].links.length; i++) {\n                var link_id = this.outputs[slot].links[i];\n\t\t\t\tvar link = this.graph.links[link_id];\n\t\t\t\tif(link)\n\t\t\t\t\tlink.data = data;\n            }\n        }\n    };\n\n    /**\n     * sets the output data type, useful when you want to be able to overwrite the data type\n     * @method setOutputDataType\n     * @param {number} slot\n     * @param {String} datatype\n     */\n    LGraphNode.prototype.setOutputDataType = function(slot, type) {\n        if (!this.outputs) {\n            return;\n        }\n        if (slot == -1 || slot >= this.outputs.length) {\n            return;\n        }\n        var output_info = this.outputs[slot];\n        if (!output_info) {\n            return;\n        }\n        //store data in the output itself in case we want to debug\n        output_info.type = type;\n\n        //if there are connections, pass the data to the connections\n        if (this.outputs[slot].links) {\n            for (var i = 0; i < this.outputs[slot].links.length; i++) {\n                var link_id = this.outputs[slot].links[i];\n                this.graph.links[link_id].type = type;\n            }\n        }\n    };\n\n    /**\n     * Retrieves the input data (data traveling through the connection) from one slot\n     * @method getInputData\n     * @param {number} slot\n     * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link\n     * @return {*} data or if it is not connected returns undefined\n     */\n    LGraphNode.prototype.getInputData = function(slot, force_update) {\n        if (!this.inputs) {\n            return;\n        } //undefined;\n\n        if (slot >= this.inputs.length || this.inputs[slot].link == null) {\n            return;\n        }\n\n        var link_id = this.inputs[slot].link;\n        var link = this.graph.links[link_id];\n        if (!link) {\n            //bug: weird case but it happens sometimes\n            return null;\n        }\n\n        if (!force_update) {\n            return link.data;\n        }\n\n        //special case: used to extract data from the incoming connection before the graph has been executed\n        var node = this.graph.getNodeById(link.origin_id);\n        if (!node) {\n            return link.data;\n        }\n\n        if (node.updateOutputData) {\n            node.updateOutputData(link.origin_slot);\n        } else if (node.onExecute) {\n            node.onExecute();\n        }\n\n        return link.data;\n    };\n\n    /**\n     * Retrieves the input data type (in case this supports multiple input types)\n     * @method getInputDataType\n     * @param {number} slot\n     * @return {String} datatype in string format\n     */\n    LGraphNode.prototype.getInputDataType = function(slot) {\n        if (!this.inputs) {\n            return null;\n        } //undefined;\n\n        if (slot >= this.inputs.length || this.inputs[slot].link == null) {\n            return null;\n        }\n        var link_id = this.inputs[slot].link;\n        var link = this.graph.links[link_id];\n        if (!link) {\n            //bug: weird case but it happens sometimes\n            return null;\n        }\n        var node = this.graph.getNodeById(link.origin_id);\n        if (!node) {\n            return link.type;\n        }\n        var output_info = node.outputs[link.origin_slot];\n        if (output_info) {\n            return output_info.type;\n        }\n        return null;\n    };\n\n    /**\n     * Retrieves the input data from one slot using its name instead of slot number\n     * @method getInputDataByName\n     * @param {String} slot_name\n     * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link\n     * @return {*} data or if it is not connected returns null\n     */\n    LGraphNode.prototype.getInputDataByName = function(\n        slot_name,\n        force_update\n    ) {\n        var slot = this.findInputSlot(slot_name);\n        if (slot == -1) {\n            return null;\n        }\n        return this.getInputData(slot, force_update);\n    };\n\n    /**\n     * tells you if there is a connection in one input slot\n     * @method isInputConnected\n     * @param {number} slot\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isInputConnected = function(slot) {\n        if (!this.inputs) {\n            return false;\n        }\n        return slot < this.inputs.length && this.inputs[slot].link != null;\n    };\n\n    /**\n     * tells you info about an input connection (which node, type, etc)\n     * @method getInputInfo\n     * @param {number} slot\n     * @return {Object} object or null { link: id, name: string, type: string or 0 }\n     */\n    LGraphNode.prototype.getInputInfo = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot < this.inputs.length) {\n            return this.inputs[slot];\n        }\n        return null;\n    };\n\n    /**\n     * Returns the link info in the connection of an input slot\n     * @method getInputLink\n     * @param {number} slot\n     * @return {LLink} object or null\n     */\n    LGraphNode.prototype.getInputLink = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot < this.inputs.length) {\n            var slot_info = this.inputs[slot];\n\t\t\treturn this.graph.links[ slot_info.link ];\n        }\n        return null;\n    };\n\n    /**\n     * returns the node connected in the input slot\n     * @method getInputNode\n     * @param {number} slot\n     * @return {LGraphNode} node or null\n     */\n    LGraphNode.prototype.getInputNode = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot >= this.inputs.length) {\n            return null;\n        }\n        var input = this.inputs[slot];\n        if (!input || input.link === null) {\n            return null;\n        }\n        var link_info = this.graph.links[input.link];\n        if (!link_info) {\n            return null;\n        }\n        return this.graph.getNodeById(link_info.origin_id);\n    };\n\n    /**\n     * returns the value of an input with this name, otherwise checks if there is a property with that name\n     * @method getInputOrProperty\n     * @param {string} name\n     * @return {*} value\n     */\n    LGraphNode.prototype.getInputOrProperty = function(name) {\n        if (!this.inputs || !this.inputs.length) {\n            return this.properties ? this.properties[name] : null;\n        }\n\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            var input_info = this.inputs[i];\n            if (name == input_info.name && input_info.link != null) {\n                var link = this.graph.links[input_info.link];\n                if (link) {\n                    return link.data;\n                }\n            }\n        }\n        return this.properties[name];\n    };\n\n    /**\n     * tells you the last output data that went in that slot\n     * @method getOutputData\n     * @param {number} slot\n     * @return {Object}  object or null\n     */\n    LGraphNode.prototype.getOutputData = function(slot) {\n        if (!this.outputs) {\n            return null;\n        }\n        if (slot >= this.outputs.length) {\n            return null;\n        }\n\n        var info = this.outputs[slot];\n        return info._data;\n    };\n\n    /**\n     * tells you info about an output connection (which node, type, etc)\n     * @method getOutputInfo\n     * @param {number} slot\n     * @return {Object}  object or null { name: string, type: string, links: [ ids of links in number ] }\n     */\n    LGraphNode.prototype.getOutputInfo = function(slot) {\n        if (!this.outputs) {\n            return null;\n        }\n        if (slot < this.outputs.length) {\n            return this.outputs[slot];\n        }\n        return null;\n    };\n\n    /**\n     * tells you if there is a connection in one output slot\n     * @method isOutputConnected\n     * @param {number} slot\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isOutputConnected = function(slot) {\n        if (!this.outputs) {\n            return false;\n        }\n        return (\n            slot < this.outputs.length &&\n            this.outputs[slot].links &&\n            this.outputs[slot].links.length\n        );\n    };\n\n    /**\n     * tells you if there is any connection in the output slots\n     * @method isAnyOutputConnected\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isAnyOutputConnected = function() {\n        if (!this.outputs) {\n            return false;\n        }\n        for (var i = 0; i < this.outputs.length; ++i) {\n            if (this.outputs[i].links && this.outputs[i].links.length) {\n                return true;\n            }\n        }\n        return false;\n    };\n\n    /**\n     * retrieves all the nodes connected to this output slot\n     * @method getOutputNodes\n     * @param {number} slot\n     * @return {array}\n     */\n    LGraphNode.prototype.getOutputNodes = function(slot) {\n        if (!this.outputs || this.outputs.length == 0) {\n            return null;\n        }\n\n        if (slot >= this.outputs.length) {\n            return null;\n        }\n\n        var output = this.outputs[slot];\n        if (!output.links || output.links.length == 0) {\n            return null;\n        }\n\n        var r = [];\n        for (var i = 0; i < output.links.length; i++) {\n            var link_id = output.links[i];\n            var link = this.graph.links[link_id];\n            if (link) {\n                var target_node = this.graph.getNodeById(link.target_id);\n                if (target_node) {\n                    r.push(target_node);\n                }\n            }\n        }\n        return r;\n    };\n\n    LGraphNode.prototype.addOnTriggerInput = function(){\n        var trigS = this.findInputSlot(\"onTrigger\");\n        if (trigS == -1){ //!trigS || \n            var input = this.addInput(\"onTrigger\", LiteGraph.EVENT, {optional: true, nameLocked: true});\n            return this.findInputSlot(\"onTrigger\");\n        }\n        return trigS;\n    }\n    \n    LGraphNode.prototype.addOnExecutedOutput = function(){\n        var trigS = this.findOutputSlot(\"onExecuted\");\n        if (trigS == -1){ //!trigS || \n            var output = this.addOutput(\"onExecuted\", LiteGraph.ACTION, {optional: true, nameLocked: true});\n            return this.findOutputSlot(\"onExecuted\");\n        }\n        return trigS;\n    }\n    \n    LGraphNode.prototype.onAfterExecuteNode = function(param, options){\n        var trigS = this.findOutputSlot(\"onExecuted\");\n        if (trigS != -1){\n            \n            //console.debug(this.id+\":\"+this.order+\" triggering slot onAfterExecute\");\n            //console.debug(param);\n            //console.debug(options);\n            this.triggerSlot(trigS, param, null, options);\n            \n        }\n    }    \n    \n    LGraphNode.prototype.changeMode = function(modeTo){\n        switch(modeTo){\n            case LiteGraph.ON_EVENT:\n                // this.addOnExecutedOutput();\n                break;\n                \n            case LiteGraph.ON_TRIGGER:\n                this.addOnTriggerInput();\n                this.addOnExecutedOutput();\n                break;\n                \n            case LiteGraph.NEVER:\n                break;\n                \n            case LiteGraph.ALWAYS:\n                break;\n                \n            case LiteGraph.ON_REQUEST:\n                break;\n            \n            default:\n                return false;\n                break;\n        }\n        this.mode = modeTo;\n        return true;\n    };\n\n    /**\n     * Triggers the execution of actions that were deferred when the action was triggered\n     * @method executePendingActions\n     */    \n    LGraphNode.prototype.executePendingActions = function() {\n        if(!this._waiting_actions || !this._waiting_actions.length)\n            return;\n        for(var i = 0; i < this._waiting_actions.length;++i)\n        {\n            var p = this._waiting_actions[i];\n            this.onAction(p[0],p[1],p[2],p[3],p[4]);\n        }        \n        this._waiting_actions.length = 0;\n    }\n\n    \n    /**\n     * Triggers the node code execution, place a boolean/counter to mark the node as being executed\n     * @method doExecute\n     * @param {*} param\n     * @param {*} options\n     */\n    LGraphNode.prototype.doExecute = function(param, options) {\n        options = options || {};\n        if (this.onExecute){\n            \n            // enable this to give the event an ID\n\t\t\tif (!options.action_call) options.action_call = this.id+\"_exec_\"+Math.floor(Math.random()*9999);\n            \n            this.graph.nodes_executing[this.id] = true; //.push(this.id);\n\n            this.onExecute(param, options);\n            \n            this.graph.nodes_executing[this.id] = false; //.pop();\n            \n            // save execution/action ref\n            this.exec_version = this.graph.iteration;\n            if(options && options.action_call){\n                this.action_call = options.action_call; // if (param)\n                this.graph.nodes_executedAction[this.id] = options.action_call;\n            }\n        }\n        else {\n        }\n        this.execute_triggered = 2; // the nFrames it will be used (-- each step), means \"how old\" is the event\n        if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback\n    };\n    \n    /**\n     * Triggers an action, wrapped by logics to control execution flow\n     * @method actionDo\n     * @param {String} action name\n     * @param {*} param\n     */\n    LGraphNode.prototype.actionDo = function(action, param, options, action_slot ) {\n        options = options || {};\n        if (this.onAction){\n            \n\t\t\t// enable this to give the event an ID\n            if (!options.action_call) options.action_call = this.id+\"_\"+(action?action:\"action\")+\"_\"+Math.floor(Math.random()*9999);\n            \n            this.graph.nodes_actioning[this.id] = (action?action:\"actioning\"); //.push(this.id);\n            \n            this.onAction(action, param, options, action_slot);\n            \n            this.graph.nodes_actioning[this.id] = false; //.pop();\n            \n            // save execution/action ref\n            if(options && options.action_call){\n                this.action_call = options.action_call; // if (param)\n                this.graph.nodes_executedAction[this.id] = options.action_call;\n            }\n        }\n        this.action_triggered = 2; // the nFrames it will be used (-- each step), means \"how old\" is the event\n        if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options);\n    };\n    \n    /**\n     * Triggers an event in this node, this will trigger any output with the same name\n     * @method trigger\n     * @param {String} event name ( \"on_play\", ... ) if action is equivalent to false then the event is send to all\n     * @param {*} param\n     */\n    LGraphNode.prototype.trigger = function(action, param, options) {\n        if (!this.outputs || !this.outputs.length) {\n            return;\n        }\n\n        if (this.graph)\n            this.graph._last_trigger_time = LiteGraph.getTime();\n\n        for (var i = 0; i < this.outputs.length; ++i) {\n            var output = this.outputs[i];\n            if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) )\n                continue;\n            this.triggerSlot(i, param, null, options);\n        }\n    };\n\n    /**\n     * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes\n     * @method triggerSlot\n     * @param {Number} slot the index of the output slot\n     * @param {*} param\n     * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot\n     */\n    LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) {\n        options = options || {};\n        if (!this.outputs) {\n            return;\n        }\n\n\t\tif(slot == null)\n\t\t{\n\t\t\tconsole.error(\"slot must be a number\");\n\t\t\treturn;\n\t\t}\n\n\t\tif(slot.constructor !== Number)\n\t\t\tconsole.warn(\"slot must be a number, use node.trigger('name') if you want to use a string\");\n\n        var output = this.outputs[slot];\n        if (!output) {\n            return;\n        }\n\n        var links = output.links;\n        if (!links || !links.length) {\n            return;\n        }\n\n        if (this.graph) {\n            this.graph._last_trigger_time = LiteGraph.getTime();\n        }\n\n        //for every link attached here\n        for (var k = 0; k < links.length; ++k) {\n            var id = links[k];\n            if (link_id != null && link_id != id) {\n                //to skip links\n                continue;\n            }\n            var link_info = this.graph.links[links[k]];\n            if (!link_info) {\n                //not connected\n                continue;\n            }\n            link_info._last_time = LiteGraph.getTime();\n            var node = this.graph.getNodeById(link_info.target_id);\n            if (!node) {\n                //node not found?\n                continue;\n            }\n\n            //used to mark events in graph\n            var target_connection = node.inputs[link_info.target_slot];\n\n\t\t\tif (node.mode === LiteGraph.ON_TRIGGER)\n\t\t\t{\n\t\t\t\t// generate unique trigger ID if not present\n\t\t\t\tif (!options.action_call) options.action_call = this.id+\"_trigg_\"+Math.floor(Math.random()*9999);\n                if (node.onExecute) {\n                    // -- wrapping node.onExecute(param); --\n                    node.doExecute(param, options);\n                }\n\t\t\t}\n\t\t\telse if (node.onAction) {\n                // generate unique action ID if not present\n\t\t\t\tif (!options.action_call) options.action_call = this.id+\"_act_\"+Math.floor(Math.random()*9999);\n                //pass the action name\n                var target_connection = node.inputs[link_info.target_slot];\n\n                //instead of executing them now, it will be executed in the next graph loop, to ensure data flow\n                if(LiteGraph.use_deferred_actions && node.onExecute)\n                {\n                    if(!node._waiting_actions)\n                        node._waiting_actions = [];\n                    node._waiting_actions.push([target_connection.name, param, options, link_info.target_slot]);\n                }\n                else\n                {\n                    // wrap node.onAction(target_connection.name, param);\n                    node.actionDo( target_connection.name, param, options, link_info.target_slot );\n                }\n            }\n        }\n    };\n\n    /**\n     * clears the trigger slot animation\n     * @method clearTriggeredSlot\n     * @param {Number} slot the index of the output slot\n     * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot\n     */\n    LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) {\n        if (!this.outputs) {\n            return;\n        }\n\n        var output = this.outputs[slot];\n        if (!output) {\n            return;\n        }\n\n        var links = output.links;\n        if (!links || !links.length) {\n            return;\n        }\n\n        //for every link attached here\n        for (var k = 0; k < links.length; ++k) {\n            var id = links[k];\n            if (link_id != null && link_id != id) {\n                //to skip links\n                continue;\n            }\n            var link_info = this.graph.links[links[k]];\n            if (!link_info) {\n                //not connected\n                continue;\n            }\n            link_info._last_time = 0;\n        }\n    };\n\n    /**\n     * changes node size and triggers callback\n     * @method setSize\n     * @param {vec2} size\n     */\n    LGraphNode.prototype.setSize = function(size)\n\t{\n\t\tthis.size = size;\n\t\tif(this.onResize)\n\t\t\tthis.onResize(this.size);\n\t}\n\n    /**\n     * add a new property to this node\n     * @method addProperty\n     * @param {string} name\n     * @param {*} default_value\n     * @param {string} type string defining the output type (\"vec3\",\"number\",...)\n     * @param {Object} extra_info this can be used to have special properties of the property (like values, etc)\n     */\n    LGraphNode.prototype.addProperty = function(\n        name,\n        default_value,\n        type,\n        extra_info\n    ) {\n        var o = { name: name, type: type, default_value: default_value };\n        if (extra_info) {\n            for (var i in extra_info) {\n                o[i] = extra_info[i];\n            }\n        }\n        if (!this.properties_info) {\n            this.properties_info = [];\n        }\n        this.properties_info.push(o);\n        if (!this.properties) {\n            this.properties = {};\n        }\n        this.properties[name] = default_value;\n        return o;\n    };\n\n    //connections\n\n    /**\n     * add a new output slot to use in this node\n     * @method addOutput\n     * @param {string} name\n     * @param {string} type string defining the output type (\"vec3\",\"number\",...)\n     * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc)\n     */\n    LGraphNode.prototype.addOutput = function(name, type, extra_info) {\n        var output = { name: name, type: type, links: null };\n        if (extra_info) {\n            for (var i in extra_info) {\n                output[i] = extra_info[i];\n            }\n        }\n\n        if (!this.outputs) {\n            this.outputs = [];\n        }\n        this.outputs.push(output);\n        if (this.onOutputAdded) {\n            this.onOutputAdded(output);\n        }\n        \n        if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true);\n        \n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n        return output;\n    };\n\n    /**\n     * add a new output slot to use in this node\n     * @method addOutputs\n     * @param {Array} array of triplets like [[name,type,extra_info],[...]]\n     */\n    LGraphNode.prototype.addOutputs = function(array) {\n        for (var i = 0; i < array.length; ++i) {\n            var info = array[i];\n            var o = { name: info[0], type: info[1], link: null };\n            if (array[2]) {\n                for (var j in info[2]) {\n                    o[j] = info[2][j];\n                }\n            }\n\n            if (!this.outputs) {\n                this.outputs = [];\n            }\n            this.outputs.push(o);\n            if (this.onOutputAdded) {\n                this.onOutputAdded(o);\n            }\n            \n            if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true);\n            \n        }\n\n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * remove an existing output slot\n     * @method removeOutput\n     * @param {number} slot\n     */\n    LGraphNode.prototype.removeOutput = function(slot) {\n        this.disconnectOutput(slot);\n        this.outputs.splice(slot, 1);\n        for (var i = slot; i < this.outputs.length; ++i) {\n            if (!this.outputs[i] || !this.outputs[i].links) {\n                continue;\n            }\n            var links = this.outputs[i].links;\n            for (var j = 0; j < links.length; ++j) {\n                var link = this.graph.links[links[j]];\n                if (!link) {\n                    continue;\n                }\n                link.origin_slot -= 1;\n            }\n        }\n\n        this.setSize( this.computeSize() );\n        if (this.onOutputRemoved) {\n            this.onOutputRemoved(slot);\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * add a new input slot to use in this node\n     * @method addInput\n     * @param {string} name\n     * @param {string} type string defining the input type (\"vec3\",\"number\",...), it its a generic one use 0\n     * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc)\n     */\n    LGraphNode.prototype.addInput = function(name, type, extra_info) {\n        type = type || 0;\n        var input = { name: name, type: type, link: null };\n        if (extra_info) {\n            for (var i in extra_info) {\n                input[i] = extra_info[i];\n            }\n        }\n\n        if (!this.inputs) {\n            this.inputs = [];\n        }\n\n        this.inputs.push(input);\n        this.setSize( this.computeSize() );\n\n        if (this.onInputAdded) {\n            this.onInputAdded(input);\n\t\t}\n        \n        LiteGraph.registerNodeAndSlotType(this,type);\n\n        this.setDirtyCanvas(true, true);\n        return input;\n    };\n\n    /**\n     * add several new input slots in this node\n     * @method addInputs\n     * @param {Array} array of triplets like [[name,type,extra_info],[...]]\n     */\n    LGraphNode.prototype.addInputs = function(array) {\n        for (var i = 0; i < array.length; ++i) {\n            var info = array[i];\n            var o = { name: info[0], type: info[1], link: null };\n            if (array[2]) {\n                for (var j in info[2]) {\n                    o[j] = info[2][j];\n                }\n            }\n\n            if (!this.inputs) {\n                this.inputs = [];\n            }\n            this.inputs.push(o);\n            if (this.onInputAdded) {\n                this.onInputAdded(o);\n            }\n            \n            LiteGraph.registerNodeAndSlotType(this,info[1]);\n        }\n\n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * remove an existing input slot\n     * @method removeInput\n     * @param {number} slot\n     */\n    LGraphNode.prototype.removeInput = function(slot) {\n        this.disconnectInput(slot);\n        var slot_info = this.inputs.splice(slot, 1);\n        for (var i = slot; i < this.inputs.length; ++i) {\n            if (!this.inputs[i]) {\n                continue;\n            }\n            var link = this.graph.links[this.inputs[i].link];\n            if (!link) {\n                continue;\n            }\n            link.target_slot -= 1;\n        }\n        this.setSize( this.computeSize() );\n        if (this.onInputRemoved) {\n            this.onInputRemoved(slot, slot_info[0] );\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * add an special connection to this node (used for special kinds of graphs)\n     * @method addConnection\n     * @param {string} name\n     * @param {string} type string defining the input type (\"vec3\",\"number\",...)\n     * @param {[x,y]} pos position of the connection inside the node\n     * @param {string} direction if is input or output\n     */\n    LGraphNode.prototype.addConnection = function(name, type, pos, direction) {\n        var o = {\n            name: name,\n            type: type,\n            pos: pos,\n            direction: direction,\n            links: null\n        };\n        this.connections.push(o);\n        return o;\n    };\n\n    /**\n     * computes the minimum size of a node according to its inputs and output slots\n     * @method computeSize\n     * @param {vec2} minHeight\n     * @return {vec2} the total size\n     */\n    LGraphNode.prototype.computeSize = function(out) {\n        if (this.constructor.size) {\n            return this.constructor.size.concat();\n        }\n\n        var rows = Math.max(\n            this.inputs ? this.inputs.length : 1,\n            this.outputs ? this.outputs.length : 1\n        );\n        var size = out || new Float32Array([0, 0]);\n        rows = Math.max(rows, 1);\n        var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size\n\n        var title_width = compute_text_size(this.title);\n        var input_width = 0;\n        var output_width = 0;\n\n        if (this.inputs) {\n            for (var i = 0, l = this.inputs.length; i < l; ++i) {\n                var input = this.inputs[i];\n                var text = input.label || input.name || \"\";\n                var text_width = compute_text_size(text);\n                if (input_width < text_width) {\n                    input_width = text_width;\n                }\n            }\n        }\n\n        if (this.outputs) {\n            for (var i = 0, l = this.outputs.length; i < l; ++i) {\n                var output = this.outputs[i];\n                var text = output.label || output.name || \"\";\n                var text_width = compute_text_size(text);\n                if (output_width < text_width) {\n                    output_width = text_width;\n                }\n            }\n        }\n\n        size[0] = Math.max(input_width + output_width + 10, title_width);\n        size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH);\n        if (this.widgets && this.widgets.length) {\n            size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5);\n        }\n\n        size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT;\n\n        var widgets_height = 0;\n        if (this.widgets && this.widgets.length) {\n            for (var i = 0, l = this.widgets.length; i < l; ++i) {\n                if (this.widgets[i].computeSize)\n                    widgets_height += this.widgets[i].computeSize(size[0])[1] + 4;\n                else\n                    widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4;\n            }\n            widgets_height += 8;\n        }\n\n        //compute height using widgets height\n        if( this.widgets_up )\n            size[1] = Math.max( size[1], widgets_height );\n        else if( this.widgets_start_y != null )\n            size[1] = Math.max( size[1], widgets_height + this.widgets_start_y );\n        else\n            size[1] += widgets_height;\n\n        function compute_text_size(text) {\n            if (!text) {\n                return 0;\n            }\n            return font_size * text.length * 0.6;\n        }\n\n        if (\n            this.constructor.min_height &&\n            size[1] < this.constructor.min_height\n        ) {\n            size[1] = this.constructor.min_height;\n        }\n\n        size[1] += 6; //margin\n\n        return size;\n    };\n\n    /**\n     * returns all the info available about a property of this node.\n     *\n     * @method getPropertyInfo\n     * @param {String} property name of the property\n     * @return {Object} the object with all the available info\n    */\n    LGraphNode.prototype.getPropertyInfo = function( property )\n\t{\n        var info = null;\n\n\t\t//there are several ways to define info about a property\n\t\t//legacy mode\n\t\tif (this.properties_info) {\n            for (var i = 0; i < this.properties_info.length; ++i) {\n                if (this.properties_info[i].name == property) {\n                    info = this.properties_info[i];\n                    break;\n                }\n            }\n        }\n\t\t//litescene mode using the constructor\n\t\tif(this.constructor[\"@\" + property])\n\t\t\tinfo = this.constructor[\"@\" + property];\n\n\t\tif(this.constructor.widgets_info && this.constructor.widgets_info[property])\n\t\t\tinfo = this.constructor.widgets_info[property];\n\n\t\t//litescene mode using the constructor\n\t\tif (!info && this.onGetPropertyInfo) {\n            info = this.onGetPropertyInfo(property);\n        }\n\n        if (!info)\n            info = {};\n\t\tif(!info.type)\n\t\t\tinfo.type = typeof this.properties[property];\n\t\tif(info.widget == \"combo\")\n\t\t\tinfo.type = \"enum\";\n\n\t\treturn info;\n\t}\n\n    /**\n     * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties\n     *\n     * @method addWidget\n     * @param {String} type the widget type (could be \"number\",\"string\",\"combo\"\n     * @param {String} name the text to show on the widget\n     * @param {String} value the default value\n     * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify)\n     * @param {Object} options the object that contains special properties of this widget \n     * @return {Object} the created widget object\n     */\n    LGraphNode.prototype.addWidget = function( type, name, value, callback, options )\n\t{\n        if (!this.widgets) {\n            this.widgets = [];\n        }\n\n\t\tif(!options && callback && callback.constructor === Object)\n\t\t{\n\t\t\toptions = callback;\n\t\t\tcallback = null;\n\t\t}\n\n\t\tif(options && options.constructor === String) //options can be the property name\n\t\t\toptions = { property: options };\n\n\t\tif(callback && callback.constructor === String) //callback can be the property name\n\t\t{\n\t\t\tif(!options)\n\t\t\t\toptions = {};\n\t\t\toptions.property = callback;\n\t\t\tcallback = null;\n\t\t}\n\n\t\tif(callback && callback.constructor !== Function)\n\t\t{\n\t\t\tconsole.warn(\"addWidget: callback must be a function\");\n\t\t\tcallback = null;\n\t\t}\n\n        var w = {\n            type: type.toLowerCase(),\n            name: name,\n            value: value,\n            callback: callback,\n            options: options || {}\n        };\n\n        if (w.options.y !== undefined) {\n            w.y = w.options.y;\n        }\n\n        if (!callback && !w.options.callback && !w.options.property) {\n            console.warn(\"LiteGraph addWidget(...) without a callback or property assigned\");\n        }\n        if (type == \"combo\" && !w.options.values) {\n            throw \"LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }\";\n        }\n        this.widgets.push(w);\n\t\tthis.setSize( this.computeSize() );\n        return w;\n    };\n\n    LGraphNode.prototype.addCustomWidget = function(custom_widget) {\n        if (!this.widgets) {\n            this.widgets = [];\n        }\n        this.widgets.push(custom_widget);\n        return custom_widget;\n    };\n\n    /**\n     * returns the bounding of the object, used for rendering purposes\n     * @method getBounding\n     * @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage\n     * @param compute_outer {boolean?} [optional] set to true to include the shadow and connection points in the bounding calculation\n     * @return {Float32Array[4]} the bounding box in format of [topleft_cornerx, topleft_cornery, width, height]\n     */\n    LGraphNode.prototype.getBounding = function(out, compute_outer) {\n        out = out || new Float32Array(4);\n        const nodePos = this.pos;\n        const isCollapsed = this.flags.collapsed;\n        const nodeSize = this.size;\n        \n        let left_offset = 0;\n        // 1 offset due to how nodes are rendered\n        let right_offset =  1 ;\n        let top_offset = 0;\n        let bottom_offset = 0;\n        \n        if (compute_outer) {\n            // 4 offset for collapsed node connection points\n            left_offset = 4;\n            // 6 offset for right shadow and collapsed node connection points\n            right_offset = 6 + left_offset;\n            // 4 offset for collapsed nodes top connection points\n            top_offset = 4;\n            // 5 offset for bottom shadow and collapsed node connection points\n            bottom_offset = 5 + top_offset;\n        }\n        \n        out[0] = nodePos[0] - left_offset;\n        out[1] = nodePos[1] - LiteGraph.NODE_TITLE_HEIGHT - top_offset;\n        out[2] = isCollapsed ?\n            (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + right_offset :\n            nodeSize[0] + right_offset;\n        out[3] = isCollapsed ?\n            LiteGraph.NODE_TITLE_HEIGHT + bottom_offset :\n            nodeSize[1] + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset;\n\n        if (this.onBounding) {\n            this.onBounding(out);\n        }\n        return out;\n    };\n\n    /**\n     * checks if a point is inside the shape of a node\n     * @method isPointInside\n     * @param {number} x\n     * @param {number} y\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) {\n        margin = margin || 0;\n\n        var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT;\n        if (skip_title) {\n            margin_top = 0;\n        }\n        if (this.flags && this.flags.collapsed) {\n            //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS)\n            if (\n                isInsideRectangle(\n                    x,\n                    y,\n                    this.pos[0] - margin,\n                    this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin,\n                    (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) +\n                        2 * margin,\n                    LiteGraph.NODE_TITLE_HEIGHT + 2 * margin\n                )\n            ) {\n                return true;\n            }\n        } else if (\n            this.pos[0] - 4 - margin < x &&\n            this.pos[0] + this.size[0] + 4 + margin > x &&\n            this.pos[1] - margin_top - margin < y &&\n            this.pos[1] + this.size[1] + margin > y\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    /**\n     * checks if a point is inside a node slot, and returns info about which slot\n     * @method getSlotInPosition\n     * @param {number} x\n     * @param {number} y\n     * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }\n     */\n    LGraphNode.prototype.getSlotInPosition = function(x, y) {\n        //search for inputs\n        var link_pos = new Float32Array(2);\n        if (this.inputs) {\n            for (var i = 0, l = this.inputs.length; i < l; ++i) {\n                var input = this.inputs[i];\n                this.getConnectionPos(true, i, link_pos);\n                if (\n                    isInsideRectangle(\n                        x,\n                        y,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        20,\n                        10\n                    )\n                ) {\n                    return { input: input, slot: i, link_pos: link_pos };\n                }\n            }\n        }\n\n        if (this.outputs) {\n            for (var i = 0, l = this.outputs.length; i < l; ++i) {\n                var output = this.outputs[i];\n                this.getConnectionPos(false, i, link_pos);\n                if (\n                    isInsideRectangle(\n                        x,\n                        y,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        20,\n                        10\n                    )\n                ) {\n                    return { output: output, slot: i, link_pos: link_pos };\n                }\n            }\n        }\n\n        return null;\n    };\n\n    /**\n     * returns the input slot with a given name (used for dynamic slots), -1 if not found\n     * @method findInputSlot\n     * @param {string} name the name of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findInputSlot = function(name,  returnObj) {\n        if (!this.inputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            if (name == this.inputs[i].name) {\n                return !returnObj ? i : this.inputs[i];\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * returns the output slot with a given name (used for dynamic slots), -1 if not found\n     * @method findOutputSlot\n     * @param {string} name the name of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findOutputSlot = function(name, returnObj) {\n        returnObj = returnObj || false;\n        if (!this.outputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            if (name == this.outputs[i].name) {\n                return !returnObj ? i : this.outputs[i];\n            }\n        }\n        return -1;\n    };\n    \n    // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options\n    \n    /**\n     * returns the first free input slot\n     * @method findInputSlotFree\n     * @param {object} options\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findInputSlotFree = function(optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = {returnObj: false\n                        ,typesNotAccepted: []\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (!this.inputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            if (this.inputs[i].link && this.inputs[i].link != null) {\n                continue;\n            }\n            if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){\n                continue;\n            }\n            return !opts.returnObj ? i : this.inputs[i];\n        }\n        return -1;\n    };\n\n    /**\n     * returns the first output slot free\n     * @method findOutputSlotFree\n     * @param {object} options\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findOutputSlotFree = function(optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { returnObj: false\n                        ,typesNotAccepted: []\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (!this.outputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            if (this.outputs[i].links && this.outputs[i].links != null) {\n                continue;\n            }\n            if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){\n                continue;\n            }\n            return !opts.returnObj ? i : this.outputs[i];\n        }\n        return -1;\n    };\n    \n    /**\n     * findSlotByType for INPUTS\n     */\n    LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied);\n    };\n\n    /**\n     * findSlotByType for OUTPUTS\n     */\n    LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied);\n    };\n    \n    /**\n     * returns the output (or input) slot with a given type, -1 if not found\n     * @method findSlotByType\n     * @param {boolean} input uise inputs instead of outputs\n     * @param {string} type the type of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway)\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        input = input || false;\n        returnObj = returnObj || false;\n        preferFreeSlot = preferFreeSlot || false;\n        doNotUseOccupied = doNotUseOccupied || false;\n        var aSlots = input ? this.inputs : this.outputs;\n        if (!aSlots) {\n            return -1;\n        }\n\t\t// !! empty string type is considered 0, * !!\n\t\tif (type == \"\" || type == \"*\") type = 0; \n        for (var i = 0, l = aSlots.length; i < l; ++i) {\n            var tFound = false;\n            var aSource = (type+\"\").toLowerCase().split(\",\");\n            var aDest = aSlots[i].type==\"0\"||aSlots[i].type==\"*\"?\"0\":aSlots[i].type;\n\t\t\taDest = (aDest+\"\").toLowerCase().split(\",\");\n            for(var sI=0;sI<aSource.length;sI++){\n                for(var dI=0;dI<aDest.length;dI++){\n\t\t\t\t\tif (aSource[sI]==\"_event_\") aSource[sI] = LiteGraph.EVENT;\n\t\t\t\t\tif (aDest[sI]==\"_event_\") aDest[sI] = LiteGraph.EVENT;\n\t\t\t\t\tif (aSource[sI]==\"*\") aSource[sI] = 0;\n\t\t\t\t\tif (aDest[sI]==\"*\") aDest[sI] = 0;\n\t\t\t\t\tif (aSource[sI] == aDest[dI]) {\n                        if (preferFreeSlot && aSlots[i].links && aSlots[i].links !== null) continue;\n                        return !returnObj ? i : aSlots[i];\n                    }\n                }\n            }\n        }\n        // if didnt find some, stop checking for free slots\n        if (preferFreeSlot && !doNotUseOccupied){\n            for (var i = 0, l = aSlots.length; i < l; ++i) {\n                var tFound = false;\n                var aSource = (type+\"\").toLowerCase().split(\",\");\n                var aDest = aSlots[i].type==\"0\"||aSlots[i].type==\"*\"?\"0\":aSlots[i].type;\n\t\t\t\taDest = (aDest+\"\").toLowerCase().split(\",\");\n                for(var sI=0;sI<aSource.length;sI++){\n                    for(var dI=0;dI<aDest.length;dI++){\n\t\t\t\t\t\tif (aSource[sI]==\"*\") aSource[sI] = 0;\n\t\t\t\t\t\tif (aDest[sI]==\"*\") aDest[sI] = 0;\n                        if (aSource[sI] == aDest[dI]) {\n                            return !returnObj ? i : aSlots[i];\n                        }\n                    }\n                }\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * connect this node output to the input of another node BY TYPE\n     * @method connectByType\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {string} target_type the input slot type of the target node\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connectByType = function(slot, target_node, target_slotType, optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { createEventInCase: true\n\t\t\t\t\t   \t,firstFreeIfOutputGeneralInCase: true\n                        ,generalTypeInCase: true\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (target_node && target_node.constructor === Number) {\n            target_node = this.graph.getNodeById(target_node);\n        }\n        var target_slot = target_node.findInputSlotByType(target_slotType, false, true);\n        if (target_slot >= 0 && target_slot !== null){\n            //console.debug(\"CONNbyTYPE type \"+target_slotType+\" for \"+target_slot)\n            return this.connect(slot, target_node, target_slot);\n        }else{\n            //console.log(\"type \"+target_slotType+\" not found or not free?\")\n            if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){\n                // WILL CREATE THE onTrigger IN SLOT\n\t\t\t\t//console.debug(\"connect WILL CREATE THE onTrigger \"+target_slotType+\" to \"+target_node);\n                return this.connect(slot, target_node, -1);\n            }\n\t\t\t// connect to the first general output slot if not found a specific type and \n            if (opts.generalTypeInCase){\n                var target_slot = target_node.findInputSlotByType(0, false, true, true);\n\t\t\t\t//console.debug(\"connect TO a general type (*, 0), if not found the specific type \",target_slotType,\" to \",target_node,\"RES_SLOT:\",target_slot);\n                if (target_slot >= 0){\n                    return this.connect(slot, target_node, target_slot);\n                }\n            }\n            // connect to the first free input slot if not found a specific type and this output is general\n            if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == \"*\" || target_slotType == \"\")){\n                var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] });\n\t\t\t\t//console.debug(\"connect TO TheFirstFREE \",target_slotType,\" to \",target_node,\"RES_SLOT:\",target_slot);\n                if (target_slot >= 0){\n\t\t\t\t\treturn this.connect(slot, target_node, target_slot);\n                }\n            }\n\t\t\t\n\t\t\tconsole.debug(\"no way to connect type: \",target_slotType,\" to targetNODE \",target_node);\n\t\t\t//TODO filter\n\t\t\t\n            return null;\n        }\n    }\n    \n    /**\n     * connect this node input to the output of another node BY TYPE\n     * @method connectByType\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {string} target_type the output slot type of the target node\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { createEventInCase: true\n                        ,firstFreeIfInputGeneralInCase: true\n                        ,generalTypeInCase: true\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (source_node && source_node.constructor === Number) {\n            source_node = this.graph.getNodeById(source_node);\n        }\n        var source_slot = source_node.findOutputSlotByType(source_slotType, false, true);\n        if (source_slot >= 0 && source_slot !== null){\n            //console.debug(\"CONNbyTYPE OUT! type \"+source_slotType+\" for \"+source_slot)\n            return source_node.connect(source_slot, this, slot);\n        }else{\n            \n            // connect to the first general output slot if not found a specific type and \n            if (opts.generalTypeInCase){\n                var source_slot = source_node.findOutputSlotByType(0, false, true, true);\n                if (source_slot >= 0){\n                    return source_node.connect(source_slot, this, slot);\n                }\n            }\n            \n            if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){\n                // WILL CREATE THE onExecuted OUT SLOT\n\t\t\t\tif (LiteGraph.do_add_triggers_slots){\n\t\t\t\t\tvar source_slot = source_node.addOnExecutedOutput();\n\t\t\t\t\treturn source_node.connect(source_slot, this, slot);\n\t\t\t\t}\n            }\n            // connect to the first free output slot if not found a specific type and this input is general\n            if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == \"*\" || source_slotType == \"\")){\n                var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] });\n                if (source_slot >= 0){\n                    return source_node.connect(source_slot, this, slot);\n                }\n            }\n            \n\t\t\tconsole.debug(\"no way to connect byOUT type: \",source_slotType,\" to sourceNODE \",source_node);\n\t\t\t//TODO filter\n\t\t\t\n            //console.log(\"type OUT! \"+source_slotType+\" not found or not free?\")\n            return null;\n        }\n    }\n    \n    /**\n     * connect this node output to the input of another node\n     * @method connect\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connect = function(slot, target_node, target_slot) {\n        target_slot = target_slot || 0;\n\n        if (!this.graph) {\n            //could be connected before adding it to a graph\n            console.log(\n                \"Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them.\"\n            ); //due to link ids being associated with graphs\n            return null;\n        }\n\n        //seek for the output slot\n        if (slot.constructor === String) {\n            slot = this.findOutputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return null;\n            }\n        } else if (!this.outputs || slot >= this.outputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return null;\n        }\n\n        if (target_node && target_node.constructor === Number) {\n            target_node = this.graph.getNodeById(target_node);\n        }\n        if (!target_node) {\n            throw \"target node is null\";\n        }\n\n        //avoid loopback\n        if (target_node == this) {\n            return null;\n        }\n\n        //you can specify the slot by name\n        if (target_slot.constructor === String) {\n            target_slot = target_node.findInputSlot(target_slot);\n            if (target_slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\n                        \"Connect: Error, no slot of name \" + target_slot\n                    );\n                }\n                return null;\n            }\n        } else if (target_slot === LiteGraph.EVENT) {\n            \n            if (LiteGraph.do_add_triggers_slots){\n\t            //search for first slot with event? :: NO this is done outside\n\t\t\t\t//console.log(\"Connect: Creating triggerEvent\");\n\t            // force mode\n\t            target_node.changeMode(LiteGraph.ON_TRIGGER);\n\t            target_slot = target_node.findInputSlot(\"onTrigger\");\n        \t}else{\n            \treturn null; // -- break --\n\t\t\t}\n        } else if (\n            !target_node.inputs ||\n            target_slot >= target_node.inputs.length\n        ) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return null;\n        }\n\n\t\tvar changed = false;\n\n        var input = target_node.inputs[target_slot];\n        var link_info = null;\n        var output = this.outputs[slot];\n        \n        if (!this.outputs[slot]){\n            /*console.debug(\"Invalid slot passed: \"+slot);\n            console.debug(this.outputs);*/\n            return null;\n        }\n\n        // allow target node to change slot\n        if (target_node.onBeforeConnectInput) {\n            // This way node can choose another slot (or make a new one?)\n            target_slot = target_node.onBeforeConnectInput(target_slot); //callback\n        }\n\n\t\t//check target_slot and check connection types\n        if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type))\n\t\t{\n\t        this.setDirtyCanvas(false, true);\n\t\t\tif(changed)\n\t\t        this.graph.connectionChange(this, link_info);\n\t\t\treturn null;\n\t\t}else{\n\t\t\t//console.debug(\"valid connection\",output.type, input.type);\n\t\t}\n\n        //allows nodes to block connection, callback\n        if (target_node.onConnectInput) {\n            if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) {\n                return null;\n            }\n        }\n        if (this.onConnectOutput) { // callback\n            if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) {\n                return null;\n            }\n        }\n\n        //if there is something already plugged there, disconnect\n        if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) {\n\t\t\tthis.graph.beforeChange();\n            target_node.disconnectInput(target_slot, {doProcessChange: false});\n\t\t\tchanged = true;\n        }\n        if (output.links !== null && output.links.length){\n            switch(output.type){\n                case LiteGraph.EVENT:\n                    if (!LiteGraph.allow_multi_output_for_events){\n                        this.graph.beforeChange();\n                        this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false});\n                        changed = true;\n                    }\n                break;\n                default:\n                break;\n            }\n        }\n\n        var nextId\n        if (LiteGraph.use_uuids)\n            nextId = LiteGraph.uuidv4();\n        else\n            nextId = ++this.graph.last_link_id;\n        \n\t\t//create link class\n\t\tlink_info = new LLink(\n\t\t\tnextId,\n\t\t\tinput.type || output.type,\n\t\t\tthis.id,\n\t\t\tslot,\n\t\t\ttarget_node.id,\n\t\t\ttarget_slot\n\t\t);\n\n\t\t//add to graph links list\n\t\tthis.graph.links[link_info.id] = link_info;\n\n\t\t//connect in output\n\t\tif (output.links == null) {\n\t\t\toutput.links = [];\n\t\t}\n\t\toutput.links.push(link_info.id);\n\t\t//connect in input\n\t\ttarget_node.inputs[target_slot].link = link_info.id;\n\t\tif (this.graph) {\n\t\t\tthis.graph._version++;\n\t\t}\n\t\tif (this.onConnectionsChange) {\n\t\t\tthis.onConnectionsChange(\n\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\tslot,\n\t\t\t\ttrue,\n\t\t\t\tlink_info,\n\t\t\t\toutput\n\t\t\t);\n\t\t} //link_info has been created now, so its updated\n\t\tif (target_node.onConnectionsChange) {\n\t\t\ttarget_node.onConnectionsChange(\n\t\t\t\tLiteGraph.INPUT,\n\t\t\t\ttarget_slot,\n\t\t\t\ttrue,\n\t\t\t\tlink_info,\n\t\t\t\tinput\n\t\t\t);\n\t\t}\n\t\tif (this.graph && this.graph.onNodeConnectionChange) {\n\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\tLiteGraph.INPUT,\n\t\t\t\ttarget_node,\n\t\t\t\ttarget_slot,\n\t\t\t\tthis,\n\t\t\t\tslot\n\t\t\t);\n\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\tthis,\n\t\t\t\tslot,\n\t\t\t\ttarget_node,\n\t\t\t\ttarget_slot\n\t\t\t);\n\t\t}\n\n        this.setDirtyCanvas(false, true);\n\t\tthis.graph.afterChange();\n\t\tthis.graph.connectionChange(this, link_info);\n\n        return link_info;\n    };\n\n    /**\n     * disconnect one output to an specific node\n     * @method disconnectOutput\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]\n     * @return {boolean} if it was disconnected successfully\n     */\n    LGraphNode.prototype.disconnectOutput = function(slot, target_node) {\n        if (slot.constructor === String) {\n            slot = this.findOutputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return false;\n            }\n        } else if (!this.outputs || slot >= this.outputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return false;\n        }\n\n        //get output slot\n        var output = this.outputs[slot];\n        if (!output || !output.links || output.links.length == 0) {\n            return false;\n        }\n\n        //one of the output links in this slot\n        if (target_node) {\n            if (target_node.constructor === Number) {\n                target_node = this.graph.getNodeById(target_node);\n            }\n            if (!target_node) {\n                throw \"Target Node not found\";\n            }\n\n            for (var i = 0, l = output.links.length; i < l; i++) {\n                var link_id = output.links[i];\n                var link_info = this.graph.links[link_id];\n\n                //is the link we are searching for...\n                if (link_info.target_id == target_node.id) {\n                    output.links.splice(i, 1); //remove here\n                    var input = target_node.inputs[link_info.target_slot];\n                    input.link = null; //remove there\n                    delete this.graph.links[link_id]; //remove the link from the links pool\n                    if (this.graph) {\n                        this.graph._version++;\n                    }\n                    if (target_node.onConnectionsChange) {\n                        target_node.onConnectionsChange(\n                            LiteGraph.INPUT,\n                            link_info.target_slot,\n                            false,\n                            link_info,\n                            input\n                        );\n                    } //link_info hasn't been modified so its ok\n                    if (this.onConnectionsChange) {\n                        this.onConnectionsChange(\n                            LiteGraph.OUTPUT,\n                            slot,\n                            false,\n                            link_info,\n                            output\n                        );\n                    }\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.OUTPUT,\n                            this,\n                            slot\n                        );\n                    }\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.OUTPUT,\n                            this,\n                            slot\n                        );\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.INPUT,\n                            target_node,\n                            link_info.target_slot\n                        );\n                    }\n                    break;\n                }\n            }\n        } //all the links in this output slot\n        else {\n            for (var i = 0, l = output.links.length; i < l; i++) {\n                var link_id = output.links[i];\n                var link_info = this.graph.links[link_id];\n                if (!link_info) {\n                    //bug: it happens sometimes\n                    continue;\n                }\n\n                var target_node = this.graph.getNodeById(link_info.target_id);\n                var input = null;\n                if (this.graph) {\n                    this.graph._version++;\n                }\n                if (target_node) {\n                    input = target_node.inputs[link_info.target_slot];\n                    input.link = null; //remove other side link\n                    if (target_node.onConnectionsChange) {\n                        target_node.onConnectionsChange(\n                            LiteGraph.INPUT,\n                            link_info.target_slot,\n                            false,\n                            link_info,\n                            input\n                        );\n                    } //link_info hasn't been modified so its ok\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.INPUT,\n                            target_node,\n                            link_info.target_slot\n                        );\n                    }\n                }\n                delete this.graph.links[link_id]; //remove the link from the links pool\n                if (this.onConnectionsChange) {\n                    this.onConnectionsChange(\n                        LiteGraph.OUTPUT,\n                        slot,\n                        false,\n                        link_info,\n                        output\n                    );\n                }\n                if (this.graph && this.graph.onNodeConnectionChange) {\n                    this.graph.onNodeConnectionChange(\n                        LiteGraph.OUTPUT,\n                        this,\n                        slot\n                    );\n                    this.graph.onNodeConnectionChange(\n                        LiteGraph.INPUT,\n                        target_node,\n                        link_info.target_slot\n                    );\n                }\n            }\n            output.links = null;\n        }\n\n        this.setDirtyCanvas(false, true);\n        this.graph.connectionChange(this);\n        return true;\n    };\n\n    /**\n     * disconnect one input\n     * @method disconnectInput\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @return {boolean} if it was disconnected successfully\n     */\n    LGraphNode.prototype.disconnectInput = function(slot) {\n        //seek for the output slot\n        if (slot.constructor === String) {\n            slot = this.findInputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return false;\n            }\n        } else if (!this.inputs || slot >= this.inputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return false;\n        }\n\n        var input = this.inputs[slot];\n        if (!input) {\n            return false;\n        }\n\n        var link_id = this.inputs[slot].link;\n\t\tif(link_id != null)\n\t\t{\n\t\t\tthis.inputs[slot].link = null;\n\n\t\t\t//remove other side\n\t\t\tvar link_info = this.graph.links[link_id];\n\t\t\tif (link_info) {\n\t\t\t\tvar target_node = this.graph.getNodeById(link_info.origin_id);\n\t\t\t\tif (!target_node) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tvar output = target_node.outputs[link_info.origin_slot];\n\t\t\t\tif (!output || !output.links || output.links.length == 0) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t//search in the inputs list for this link\n\t\t\t\tfor (var i = 0, l = output.links.length; i < l; i++) {\n\t\t\t\t\tif (output.links[i] == link_id) {\n\t\t\t\t\t\toutput.links.splice(i, 1);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tdelete this.graph.links[link_id]; //remove from the pool\n\t\t\t\tif (this.graph) {\n\t\t\t\t\tthis.graph._version++;\n\t\t\t\t}\n\t\t\t\tif (this.onConnectionsChange) {\n\t\t\t\t\tthis.onConnectionsChange(\n\t\t\t\t\t\tLiteGraph.INPUT,\n\t\t\t\t\t\tslot,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tlink_info,\n\t\t\t\t\t\tinput\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (target_node.onConnectionsChange) {\n\t\t\t\t\ttarget_node.onConnectionsChange(\n\t\t\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\t\t\ti,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tlink_info,\n\t\t\t\t\t\toutput\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (this.graph && this.graph.onNodeConnectionChange) {\n\t\t\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\t\t\ttarget_node,\n\t\t\t\t\t\ti\n\t\t\t\t\t);\n\t\t\t\t\tthis.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot);\n\t\t\t\t}\n\t\t\t}\n\t\t} //link != null\n\n        this.setDirtyCanvas(false, true);\n\t\tif(this.graph)\n\t        this.graph.connectionChange(this);\n        return true;\n    };\n\n    /**\n     * returns the center of a connection point in canvas coords\n     * @method getConnectionPos\n     * @param {boolean} is_input true if if a input slot, false if it is an output\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {vec2} out [optional] a place to store the output, to free garbage\n     * @return {[x,y]} the position\n     **/\n    LGraphNode.prototype.getConnectionPos = function(\n        is_input,\n        slot_number,\n        out\n    ) {\n        out = out || new Float32Array(2);\n        var num_slots = 0;\n        if (is_input && this.inputs) {\n            num_slots = this.inputs.length;\n        }\n        if (!is_input && this.outputs) {\n            num_slots = this.outputs.length;\n        }\n\n        var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n\n        if (this.flags.collapsed) {\n            var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;\n            if (this.horizontal) {\n                out[0] = this.pos[0] + w * 0.5;\n                if (is_input) {\n                    out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n                } else {\n                    out[1] = this.pos[1];\n                }\n            } else {\n                if (is_input) {\n                    out[0] = this.pos[0];\n                } else {\n                    out[0] = this.pos[0] + w;\n                }\n                out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            }\n            return out;\n        }\n\n        //weird feature that never got finished\n        if (is_input && slot_number == -1) {\n            out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            return out;\n        }\n\n        //hard-coded pos\n        if (\n            is_input &&\n            num_slots > slot_number &&\n            this.inputs[slot_number].pos\n        ) {\n            out[0] = this.pos[0] + this.inputs[slot_number].pos[0];\n            out[1] = this.pos[1] + this.inputs[slot_number].pos[1];\n            return out;\n        } else if (\n            !is_input &&\n            num_slots > slot_number &&\n            this.outputs[slot_number].pos\n        ) {\n            out[0] = this.pos[0] + this.outputs[slot_number].pos[0];\n            out[1] = this.pos[1] + this.outputs[slot_number].pos[1];\n            return out;\n        }\n\n        //horizontal distributed slots\n        if (this.horizontal) {\n            out[0] =\n                this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots);\n            if (is_input) {\n                out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n            } else {\n                out[1] = this.pos[1] + this.size[1];\n            }\n            return out;\n        }\n\n        //default vertical slots\n        if (is_input) {\n            out[0] = this.pos[0] + offset;\n        } else {\n            out[0] = this.pos[0] + this.size[0] + 1 - offset;\n        }\n        out[1] =\n            this.pos[1] +\n            (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT +\n            (this.constructor.slot_start_y || 0);\n        return out;\n    };\n\n    /* Force align to grid */\n    LGraphNode.prototype.alignToGrid = function() {\n        this.pos[0] =\n            LiteGraph.CANVAS_GRID_SIZE *\n            Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE);\n        this.pos[1] =\n            LiteGraph.CANVAS_GRID_SIZE *\n            Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE);\n    };\n\n    /* Console output */\n    LGraphNode.prototype.trace = function(msg) {\n        if (!this.console) {\n            this.console = [];\n        }\n\n        this.console.push(msg);\n        if (this.console.length > LGraphNode.MAX_CONSOLE) {\n            this.console.shift();\n        }\n\n\t\tif(this.graph.onNodeTrace)\n\t        this.graph.onNodeTrace(this, msg);\n    };\n\n    /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */\n    LGraphNode.prototype.setDirtyCanvas = function(\n        dirty_foreground,\n        dirty_background\n    ) {\n        if (!this.graph) {\n            return;\n        }\n        this.graph.sendActionToCanvas(\"setDirty\", [\n            dirty_foreground,\n            dirty_background\n        ]);\n    };\n\n    LGraphNode.prototype.loadImage = function(url) {\n        var img = new Image();\n        img.src = LiteGraph.node_images_path + url;\n        img.ready = false;\n\n        var that = this;\n        img.onload = function() {\n            this.ready = true;\n            that.setDirtyCanvas(true);\n        };\n        return img;\n    };\n\n    //safe LGraphNode action execution (not sure if safe)\n    /*\nLGraphNode.prototype.executeAction = function(action)\n{\n\tif(action == \"\") return false;\n\n\tif( action.indexOf(\";\") != -1 || action.indexOf(\"}\") != -1)\n\t{\n\t\tthis.trace(\"Error: Action contains unsafe characters\");\n\t\treturn false;\n\t}\n\n\tvar tokens = action.split(\"(\");\n\tvar func_name = tokens[0];\n\tif( typeof(this[func_name]) != \"function\")\n\t{\n\t\tthis.trace(\"Error: Action not found on node: \" + func_name);\n\t\treturn false;\n\t}\n\n\tvar code = action;\n\n\ttry\n\t{\n\t\tvar _foo = eval;\n\t\teval = null;\n\t\t(new Function(\"with(this) { \" + code + \"}\")).call(this);\n\t\teval = _foo;\n\t}\n\tcatch (err)\n\t{\n\t\tthis.trace(\"Error executing action {\" + action + \"} :\" + err);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n*/\n\n    /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */\n    LGraphNode.prototype.captureInput = function(v) {\n        if (!this.graph || !this.graph.list_of_graphcanvas) {\n            return;\n        }\n\n        var list = this.graph.list_of_graphcanvas;\n\n        for (var i = 0; i < list.length; ++i) {\n            var c = list[i];\n            //releasing somebody elses capture?!\n            if (!v && c.node_capturing_input != this) {\n                continue;\n            }\n\n            //change\n            c.node_capturing_input = v ? this : null;\n        }\n    };\n\n    /**\n     * Collapse the node to make it smaller on the canvas\n     * @method collapse\n     **/\n    LGraphNode.prototype.collapse = function(force) {\n        this.graph._version++;\n        if (this.constructor.collapsable === false && !force) {\n            return;\n        }\n        if (!this.flags.collapsed) {\n            this.flags.collapsed = true;\n        } else {\n            this.flags.collapsed = false;\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * Forces the node to do not move or realign on Z\n     * @method pin\n     **/\n\n    LGraphNode.prototype.pin = function(v) {\n        this.graph._version++;\n        if (v === undefined) {\n            this.flags.pinned = !this.flags.pinned;\n        } else {\n            this.flags.pinned = v;\n        }\n    };\n\n    LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) {\n        return [\n            (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0],\n            (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]\n        ];\n    };\n\n    function LGraphGroup(title) {\n        this._ctor(title);\n    }\n\n    global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup;\n\n    LGraphGroup.prototype._ctor = function(title) {\n        this.title = title || \"Group\";\n        this.font_size = 24;\n        this.color = LGraphCanvas.node_colors.pale_blue\n            ? LGraphCanvas.node_colors.pale_blue.groupcolor\n            : \"#AAA\";\n        this._bounding = new Float32Array([10, 10, 140, 80]);\n        this._pos = this._bounding.subarray(0, 2);\n        this._size = this._bounding.subarray(2, 4);\n        this._nodes = [];\n        this.graph = null;\n\n        Object.defineProperty(this, \"pos\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._pos[0] = v[0];\n                this._pos[1] = v[1];\n            },\n            get: function() {\n                return this._pos;\n            },\n            enumerable: true\n        });\n\n        Object.defineProperty(this, \"size\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._size[0] = Math.max(140, v[0]);\n                this._size[1] = Math.max(80, v[1]);\n            },\n            get: function() {\n                return this._size;\n            },\n            enumerable: true\n        });\n    };\n\n    LGraphGroup.prototype.configure = function(o) {\n        this.title = o.title;\n        this._bounding.set(o.bounding);\n        this.color = o.color;\n        this.font_size = o.font_size;\n    };\n\n    LGraphGroup.prototype.serialize = function() {\n        var b = this._bounding;\n        return {\n            title: this.title,\n            bounding: [\n                Math.round(b[0]),\n                Math.round(b[1]),\n                Math.round(b[2]),\n                Math.round(b[3])\n            ],\n            color: this.color,\n            font_size: this.font_size\n        };\n    };\n\n    LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) {\n        this._pos[0] += deltax;\n        this._pos[1] += deltay;\n        if (ignore_nodes) {\n            return;\n        }\n        for (var i = 0; i < this._nodes.length; ++i) {\n            var node = this._nodes[i];\n            node.pos[0] += deltax;\n            node.pos[1] += deltay;\n        }\n    };\n\n    LGraphGroup.prototype.recomputeInsideNodes = function() {\n        this._nodes.length = 0;\n        var nodes = this.graph._nodes;\n        var node_bounding = new Float32Array(4);\n\n        for (var i = 0; i < nodes.length; ++i) {\n            var node = nodes[i];\n            node.getBounding(node_bounding);\n            if (!overlapBounding(this._bounding, node_bounding)) {\n                continue;\n            } //out of the visible area\n            this._nodes.push(node);\n        }\n    };\n\n    LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside;\n    LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas;\n\n    //****************************************\n\n    //Scale and Offset\n    function DragAndScale(element, skip_events) {\n        this.offset = new Float32Array([0, 0]);\n        this.scale = 1;\n        this.max_scale = 10;\n        this.min_scale = 0.1;\n        this.onredraw = null;\n        this.enabled = true;\n        this.last_mouse = [0, 0];\n        this.element = null;\n        this.visible_area = new Float32Array(4);\n\n        if (element) {\n            this.element = element;\n            if (!skip_events) {\n                this.bindEvents(element);\n            }\n        }\n    }\n\n    LiteGraph.DragAndScale = DragAndScale;\n\n    DragAndScale.prototype.bindEvents = function(element) {\n        this.last_mouse = new Float32Array(2);\n\n        this._binded_mouse_callback = this.onMouse.bind(this);\n\n\t\tLiteGraph.pointerListenerAdd(element,\"down\", this._binded_mouse_callback);\n\t\tLiteGraph.pointerListenerAdd(element,\"move\", this._binded_mouse_callback);\n\t\tLiteGraph.pointerListenerAdd(element,\"up\", this._binded_mouse_callback);\n\n        element.addEventListener(\n            \"mousewheel\",\n            this._binded_mouse_callback,\n            false\n        );\n        element.addEventListener(\"wheel\", this._binded_mouse_callback, false);\n    };\n\n    DragAndScale.prototype.computeVisibleArea = function( viewport ) {\n        if (!this.element) {\n            this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0;\n            return;\n        }\n        var width = this.element.width;\n        var height = this.element.height;\n        var startx = -this.offset[0];\n        var starty = -this.offset[1];\n\t\tif( viewport )\n\t\t{\n\t\t\tstartx += viewport[0] / this.scale;\n\t\t\tstarty += viewport[1] / this.scale;\n\t\t\twidth = viewport[2];\n\t\t\theight = viewport[3];\n\t\t}\n        var endx = startx + width / this.scale;\n        var endy = starty + height / this.scale;\n        this.visible_area[0] = startx;\n        this.visible_area[1] = starty;\n        this.visible_area[2] = endx - startx;\n        this.visible_area[3] = endy - starty;\n    };\n\n    DragAndScale.prototype.onMouse = function(e) {\n        if (!this.enabled) {\n            return;\n        }\n\n        var canvas = this.element;\n        var rect = canvas.getBoundingClientRect();\n        var x = e.clientX - rect.left;\n        var y = e.clientY - rect.top;\n        e.canvasx = x;\n        e.canvasy = y;\n        e.dragging = this.dragging;\n        \n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\n\t\t//console.log(\"pointerevents: DragAndScale onMouse \"+e.type+\" \"+is_inside);\n\t\t\n        var ignore = false;\n        if (this.onmouse) {\n            ignore = this.onmouse(e);\n        }\n\n        if (e.type == LiteGraph.pointerevents_method+\"down\" && is_inside) {\n            this.dragging = true;\n\t\t\tLiteGraph.pointerListenerRemove(canvas,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(document,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(document,\"up\",this._binded_mouse_callback);\n        } else if (e.type == LiteGraph.pointerevents_method+\"move\") {\n            if (!ignore) {\n                var deltax = x - this.last_mouse[0];\n                var deltay = y - this.last_mouse[1];\n                if (this.dragging) {\n                    this.mouseDrag(deltax, deltay);\n                }\n            }\n        } else if (e.type == LiteGraph.pointerevents_method+\"up\") {\n            this.dragging = false;\n\t\t\tLiteGraph.pointerListenerRemove(document,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerRemove(document,\"up\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(canvas,\"move\",this._binded_mouse_callback);\n        } else if ( is_inside &&\n            (e.type == \"mousewheel\" ||\n            e.type == \"wheel\" ||\n            e.type == \"DOMMouseScroll\")\n        ) {\n            e.eventType = \"mousewheel\";\n            if (e.type == \"wheel\") {\n                e.wheel = -e.deltaY;\n            } else {\n                e.wheel =\n                    e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60;\n            }\n\n            //from stack overflow\n            e.delta = e.wheelDelta\n                ? e.wheelDelta / 40\n                : e.deltaY\n                ? -e.deltaY / 3\n                : 0;\n            this.changeDeltaScale(1.0 + e.delta * 0.05);\n        }\n\n        this.last_mouse[0] = x;\n        this.last_mouse[1] = y;\n\n\t\tif(is_inside)\n\t\t{\n\t        e.preventDefault();\n\t\t    e.stopPropagation();\n\t\t    return false;\n\t\t}\n    };\n\n    DragAndScale.prototype.toCanvasContext = function(ctx) {\n        ctx.scale(this.scale, this.scale);\n        ctx.translate(this.offset[0], this.offset[1]);\n    };\n\n    DragAndScale.prototype.convertOffsetToCanvas = function(pos) {\n        //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]];\n        return [\n            (pos[0] + this.offset[0]) * this.scale,\n            (pos[1] + this.offset[1]) * this.scale\n        ];\n    };\n\n    DragAndScale.prototype.convertCanvasToOffset = function(pos, out) {\n        out = out || [0, 0];\n        out[0] = pos[0] / this.scale - this.offset[0];\n        out[1] = pos[1] / this.scale - this.offset[1];\n        return out;\n    };\n\n    DragAndScale.prototype.mouseDrag = function(x, y) {\n        this.offset[0] += x / this.scale;\n        this.offset[1] += y / this.scale;\n\n        if (this.onredraw) {\n            this.onredraw(this);\n        }\n    };\n\n    DragAndScale.prototype.changeScale = function(value, zooming_center) {\n        if (value < this.min_scale) {\n            value = this.min_scale;\n        } else if (value > this.max_scale) {\n            value = this.max_scale;\n        }\n\n        if (value == this.scale) {\n            return;\n        }\n\n        if (!this.element) {\n            return;\n        }\n\n        var rect = this.element.getBoundingClientRect();\n        if (!rect) {\n            return;\n        }\n\n        zooming_center = zooming_center || [\n            rect.width * 0.5,\n            rect.height * 0.5\n        ];\n        var center = this.convertCanvasToOffset(zooming_center);\n        this.scale = value;\n        if (Math.abs(this.scale - 1) < 0.01) {\n            this.scale = 1;\n        }\n\n        var new_center = this.convertCanvasToOffset(zooming_center);\n        var delta_offset = [\n            new_center[0] - center[0],\n            new_center[1] - center[1]\n        ];\n\n        this.offset[0] += delta_offset[0];\n        this.offset[1] += delta_offset[1];\n\n        if (this.onredraw) {\n            this.onredraw(this);\n        }\n    };\n\n    DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) {\n        this.changeScale(this.scale * value, zooming_center);\n    };\n\n    DragAndScale.prototype.reset = function() {\n        this.scale = 1;\n        this.offset[0] = 0;\n        this.offset[1] = 0;\n    };\n\n    //*********************************************************************************\n    // LGraphCanvas: LGraph renderer CLASS\n    //*********************************************************************************\n\n    /**\n     * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required.\n     * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked\n     *\n     * @class LGraphCanvas\n     * @constructor\n     * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself)\n     * @param {LGraph} graph [optional]\n     * @param {Object} options [optional] { skip_rendering, autoresize, viewport }\n     */\n    function LGraphCanvas(canvas, graph, options) {\n        this.options = options = options || {};\n\n        //if(graph === undefined)\n        //\tthrow (\"No graph assigned\");\n        this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE;\n\n        if (canvas && canvas.constructor === String) {\n            canvas = document.querySelector(canvas);\n        }\n\n        this.ds = new DragAndScale();\n        this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much\n\n        this.title_text_font = \"\" + LiteGraph.NODE_TEXT_SIZE + \"px Arial\";\n        this.inner_text_font =\n            \"normal \" + LiteGraph.NODE_SUBTEXT_SIZE + \"px Arial\";\n        this.node_title_color = LiteGraph.NODE_TITLE_COLOR;\n        this.default_link_color = LiteGraph.LINK_COLOR;\n        this.default_connection_color = {\n            input_off: \"#778\",\n            input_on: \"#7F7\", //\"#BBD\"\n            output_off: \"#778\",\n            output_on: \"#7F7\" //\"#BBD\"\n\t\t};\n        this.default_connection_color_byType = {\n            /*number: \"#7F7\",\n            string: \"#77F\",\n            boolean: \"#F77\",*/\n        }\n        this.default_connection_color_byTypeOff = {\n            /*number: \"#474\",\n            string: \"#447\",\n            boolean: \"#744\",*/\n        };\n\n        this.highquality_render = true;\n        this.use_gradients = false; //set to true to render titlebar with gradients\n        this.editor_alpha = 1; //used for transition\n        this.pause_rendering = false;\n        this.clear_background = true;\n        this.clear_background_color = \"#222\";\n\n\t\tthis.read_only = false; //if set to true users cannot modify the graph\n        this.render_only_selected = true;\n        this.live_mode = false;\n        this.show_info = true;\n        this.allow_dragcanvas = true;\n        this.allow_dragnodes = true;\n        this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc\n        this.multi_select = false; //allow selecting multi nodes without pressing extra keys\n        this.allow_searchbox = true;\n        this.allow_reconnect_links = true; //allows to change a connection with having to redo it again\n\t\tthis.align_to_grid = false; //snap to grid\n\n        this.drag_mode = false;\n        this.dragging_rectangle = null;\n\n        this.filter = null; //allows to filter to only accept some type of nodes in a graph\n\n\t\tthis.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything\n        this.always_render_background = false;\n        this.render_shadows = true;\n        this.render_canvas_border = true;\n        this.render_connections_shadows = false; //too much cpu\n        this.render_connections_border = true;\n        this.render_curved_connections = false;\n        this.render_connection_arrows = false;\n        this.render_collapsed_slots = true;\n        this.render_execution_order = false;\n        this.render_title_colored = true;\n\t\tthis.render_link_tooltip = true;\n\n        this.links_render_mode = LiteGraph.SPLINE_LINK;\n\n        this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle\n        this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle\n\t\tthis.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD\n\n        //to personalize the search box\n        this.onSearchBox = null;\n        this.onSearchBoxSelection = null;\n\n        //callbacks\n        this.onMouse = null;\n        this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform\n        this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform\n        this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs)\n\t\tthis.onDrawLinkTooltip = null; //called when rendering a tooltip\n\t\tthis.onNodeMoved = null; //called after moving a node\n\t\tthis.onSelectionChange = null; //called if the selection changes\n\t\tthis.onConnectingChange = null; //called before any link changes\n\t\tthis.onBeforeChange = null; //called before modifying the graph\n\t\tthis.onAfterChange = null; //called after modifying the graph\n\n        this.connections_width = 3;\n        this.round_radius = 8;\n\n        this.current_node = null;\n        this.node_widget = null; //used for widgets\n\t\tthis.over_link_center = null;\n        this.last_mouse_position = [0, 0];\n        this.visible_area = this.ds.visible_area;\n        this.visible_links = [];\n\n\t\tthis.viewport = options.viewport || null; //to constraint render area to a portion of the canvas\n\n        //link canvas and graph\n        if (graph) {\n            graph.attachCanvas(this);\n        }\n\n        this.setCanvas(canvas,options.skip_events);\n        this.clear();\n\n        if (!options.skip_render) {\n            this.startRendering();\n        }\n\n        this.autoresize = options.autoresize;\n    }\n\n    global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas;\n\n\tLGraphCanvas.DEFAULT_BACKGROUND_IMAGE = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=\";\n\n    LGraphCanvas.link_type_colors = {\n        \"-1\": LiteGraph.EVENT_LINK_COLOR,\n        number: \"#AAA\",\n        node: \"#DCA\"\n    };\n    LGraphCanvas.gradients = {}; //cache of gradients\n\n    /**\n     * clears all the data inside\n     *\n     * @method clear\n     */\n    LGraphCanvas.prototype.clear = function() {\n        this.frame = 0;\n        this.last_draw_time = 0;\n        this.render_time = 0;\n        this.fps = 0;\n\n        //this.scale = 1;\n        //this.offset = [0,0];\n\n        this.dragging_rectangle = null;\n\n        this.selected_nodes = {};\n        this.selected_group = null;\n\n        this.visible_nodes = [];\n        this.node_dragged = null;\n        this.node_over = null;\n        this.node_capturing_input = null;\n        this.connecting_node = null;\n        this.highlighted_links = {};\n\n\t\tthis.dragging_canvas = false;\n\n        this.dirty_canvas = true;\n        this.dirty_bgcanvas = true;\n        this.dirty_area = null;\n\n        this.node_in_panel = null;\n        this.node_widget = null;\n\n        this.last_mouse = [0, 0];\n        this.last_mouseclick = 0;\n\t  \tthis.pointer_is_down = false;\n\t  \tthis.pointer_is_double = false;\n        this.visible_area.set([0, 0, 0, 0]);\n\n        if (this.onClear) {\n            this.onClear();\n        }\n    };\n\n    /**\n     * assigns a graph, you can reassign graphs to the same canvas\n     *\n     * @method setGraph\n     * @param {LGraph} graph\n     */\n    LGraphCanvas.prototype.setGraph = function(graph, skip_clear) {\n        if (this.graph == graph) {\n            return;\n        }\n\n        if (!skip_clear) {\n            this.clear();\n        }\n\n        if (!graph && this.graph) {\n            this.graph.detachCanvas(this);\n            return;\n        }\n\n        graph.attachCanvas(this);\n\n\t\t//remove the graph stack in case a subgraph was open\n\t\tif (this._graph_stack)\n\t\t\tthis._graph_stack = null;\n\n        this.setDirty(true, true);\n    };\n\n    /**\n     * returns the top level graph (in case there are subgraphs open on the canvas)\n     *\n     * @method getTopGraph\n     * @return {LGraph} graph\n     */\n\tLGraphCanvas.prototype.getTopGraph = function()\n\t{\n\t\tif(this._graph_stack.length)\n\t\t\treturn this._graph_stack[0];\n\t\treturn this.graph;\n\t}\n\n    /**\n     * opens a graph contained inside a node in the current graph\n     *\n     * @method openSubgraph\n     * @param {LGraph} graph\n     */\n    LGraphCanvas.prototype.openSubgraph = function(graph) {\n        if (!graph) {\n            throw \"graph cannot be null\";\n        }\n\n        if (this.graph == graph) {\n            throw \"graph cannot be the same\";\n        }\n\n        this.clear();\n\n        if (this.graph) {\n            if (!this._graph_stack) {\n                this._graph_stack = [];\n            }\n            this._graph_stack.push(this.graph);\n        }\n\n        graph.attachCanvas(this);\n\t\tthis.checkPanels();\n        this.setDirty(true, true);\n    };\n\n    /**\n     * closes a subgraph contained inside a node\n     *\n     * @method closeSubgraph\n     * @param {LGraph} assigns a graph\n     */\n    LGraphCanvas.prototype.closeSubgraph = function() {\n        if (!this._graph_stack || this._graph_stack.length == 0) {\n            return;\n        }\n        var subgraph_node = this.graph._subgraph_node;\n        var graph = this._graph_stack.pop();\n        this.selected_nodes = {};\n        this.highlighted_links = {};\n        graph.attachCanvas(this);\n        this.setDirty(true, true);\n        if (subgraph_node) {\n            this.centerOnNode(subgraph_node);\n            this.selectNodes([subgraph_node]);\n        }\n        // when close sub graph back to offset [0, 0] scale 1\n        this.ds.offset = [0, 0]\n        this.ds.scale = 1\n    };\n\n    /**\n     * returns the visually active graph (in case there are more in the stack)\n     * @method getCurrentGraph\n     * @return {LGraph} the active graph\n     */\n    LGraphCanvas.prototype.getCurrentGraph = function() {\n        return this.graph;\n    };\n\n    /**\n     * assigns a canvas\n     *\n     * @method setCanvas\n     * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector)\n     */\n    LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) {\n        var that = this;\n\n        if (canvas) {\n            if (canvas.constructor === String) {\n                canvas = document.getElementById(canvas);\n                if (!canvas) {\n                    throw \"Error creating LiteGraph canvas: Canvas not found\";\n                }\n            }\n        }\n\n        if (canvas === this.canvas) {\n            return;\n        }\n\n        if (!canvas && this.canvas) {\n            //maybe detach events from old_canvas\n            if (!skip_events) {\n                this.unbindEvents();\n            }\n        }\n\n        this.canvas = canvas;\n        this.ds.element = canvas;\n\n        if (!canvas) {\n            return;\n        }\n\n        //this.canvas.tabindex = \"1000\";\n        canvas.className += \" lgraphcanvas\";\n        canvas.data = this;\n        canvas.tabindex = \"1\"; //to allow key events\n\n        //bg canvas: used for non changing stuff\n        this.bgcanvas = null;\n        if (!this.bgcanvas) {\n            this.bgcanvas = document.createElement(\"canvas\");\n            this.bgcanvas.width = this.canvas.width;\n            this.bgcanvas.height = this.canvas.height;\n        }\n\n        if (canvas.getContext == null) {\n            if (canvas.localName != \"canvas\") {\n                throw \"Element supplied for LGraphCanvas must be a <canvas> element, you passed a \" +\n                    canvas.localName;\n            }\n            throw \"This browser doesn't support Canvas\";\n        }\n\n        var ctx = (this.ctx = canvas.getContext(\"2d\"));\n        if (ctx == null) {\n            if (!canvas.webgl_enabled) {\n                console.warn(\n                    \"This canvas seems to be WebGL, enabling WebGL renderer\"\n                );\n            }\n            this.enableWebGL();\n        }\n\n        //input:  (move and up could be unbinded)\n        // why here? this._mousemove_callback = this.processMouseMove.bind(this);\n        // why here? this._mouseup_callback = this.processMouseUp.bind(this);\n\n        if (!skip_events) {\n            this.bindEvents();\n        }\n    };\n\n    //used in some events to capture them\n    LGraphCanvas.prototype._doNothing = function doNothing(e) {\n    \t//console.log(\"pointerevents: _doNothing \"+e.type);\n        e.preventDefault();\n        return false;\n    };\n    LGraphCanvas.prototype._doReturnTrue = function doNothing(e) {\n        e.preventDefault();\n        return true;\n    };\n\n    /**\n     * binds mouse, keyboard, touch and drag events to the canvas\n     * @method bindEvents\n     **/\n    LGraphCanvas.prototype.bindEvents = function() {\n        if (this._events_binded) {\n            console.warn(\"LGraphCanvas: events already binded\");\n            return;\n        }\n\n        //console.log(\"pointerevents: bindEvents\");\n        \n        var canvas = this.canvas;\n\n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document; //hack used when moving canvas between windows\n\n        this._mousedown_callback = this.processMouseDown.bind(this);\n        this._mousewheel_callback = this.processMouseWheel.bind(this);\n        // why mousemove and mouseup were not binded here?\n        this._mousemove_callback = this.processMouseMove.bind(this);\n        this._mouseup_callback = this.processMouseUp.bind(this);\n        \n        //touch events -- TODO IMPLEMENT\n        //this._touch_callback = this.touchHandler.bind(this);\n\n\t\tLiteGraph.pointerListenerAdd(canvas,\"down\", this._mousedown_callback, true); //down do not need to store the binded\n        canvas.addEventListener(\"mousewheel\", this._mousewheel_callback, false);\n\n        LiteGraph.pointerListenerAdd(canvas,\"up\", this._mouseup_callback, true); // CHECK: ??? binded or not\n\t\tLiteGraph.pointerListenerAdd(canvas,\"move\", this._mousemove_callback);\n        \n        canvas.addEventListener(\"contextmenu\", this._doNothing);\n        canvas.addEventListener(\n            \"DOMMouseScroll\",\n            this._mousewheel_callback,\n            false\n        );\n\n        //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents\n        /*if( 'touchstart' in document.documentElement )\n        {\n            canvas.addEventListener(\"touchstart\", this._touch_callback, true);\n            canvas.addEventListener(\"touchmove\", this._touch_callback, true);\n            canvas.addEventListener(\"touchend\", this._touch_callback, true);\n            canvas.addEventListener(\"touchcancel\", this._touch_callback, true);\n        }*/\n\n        //Keyboard ******************\n        this._key_callback = this.processKey.bind(this);\n        canvas.setAttribute(\"tabindex\",1); //otherwise key events are ignored\n        canvas.addEventListener(\"keydown\", this._key_callback, true);\n        document.addEventListener(\"keyup\", this._key_callback, true); //in document, otherwise it doesn't fire keyup\n\n        //Dropping Stuff over nodes ************************************\n        this._ondrop_callback = this.processDrop.bind(this);\n\n        canvas.addEventListener(\"dragover\", this._doNothing, false);\n        canvas.addEventListener(\"dragend\", this._doNothing, false);\n        canvas.addEventListener(\"drop\", this._ondrop_callback, false);\n        canvas.addEventListener(\"dragenter\", this._doReturnTrue, false);\n\n        this._events_binded = true;\n    };\n\n    /**\n     * unbinds mouse events from the canvas\n     * @method unbindEvents\n     **/\n    LGraphCanvas.prototype.unbindEvents = function() {\n        if (!this._events_binded) {\n            console.warn(\"LGraphCanvas: no events binded\");\n            return;\n        }\n\n        //console.log(\"pointerevents: unbindEvents\");\n        \n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document;\n\n\t\tLiteGraph.pointerListenerRemove(this.canvas,\"move\", this._mousedown_callback);\n        LiteGraph.pointerListenerRemove(this.canvas,\"up\", this._mousedown_callback);\n        LiteGraph.pointerListenerRemove(this.canvas,\"down\", this._mousedown_callback);\n        this.canvas.removeEventListener(\n            \"mousewheel\",\n            this._mousewheel_callback\n        );\n        this.canvas.removeEventListener(\n            \"DOMMouseScroll\",\n            this._mousewheel_callback\n        );\n        this.canvas.removeEventListener(\"keydown\", this._key_callback);\n        document.removeEventListener(\"keyup\", this._key_callback);\n        this.canvas.removeEventListener(\"contextmenu\", this._doNothing);\n        this.canvas.removeEventListener(\"drop\", this._ondrop_callback);\n        this.canvas.removeEventListener(\"dragenter\", this._doReturnTrue);\n\n        //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents\n        /*this.canvas.removeEventListener(\"touchstart\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchmove\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchend\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchcancel\", this._touch_callback );*/\n\n        this._mousedown_callback = null;\n        this._mousewheel_callback = null;\n        this._key_callback = null;\n        this._ondrop_callback = null;\n\n        this._events_binded = false;\n    };\n\n    LGraphCanvas.getFileExtension = function(url) {\n        var question = url.indexOf(\"?\");\n        if (question != -1) {\n            url = url.substr(0, question);\n        }\n        var point = url.lastIndexOf(\".\");\n        if (point == -1) {\n            return \"\";\n        }\n        return url.substr(point + 1).toLowerCase();\n    };\n\n    /**\n     * this function allows to render the canvas using WebGL instead of Canvas2D\n     * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL\n     * @method enableWebGL\n     **/\n    LGraphCanvas.prototype.enableWebGL = function() {\n        if (typeof GL === \"undefined\") {\n            throw \"litegl.js must be included to use a WebGL canvas\";\n        }\n        if (typeof enableWebGLCanvas === \"undefined\") {\n            throw \"webglCanvas.js must be included to use this feature\";\n        }\n\n        this.gl = this.ctx = enableWebGLCanvas(this.canvas);\n        this.ctx.webgl = true;\n        this.bgcanvas = this.canvas;\n        this.bgctx = this.gl;\n        this.canvas.webgl_enabled = true;\n\n        /*\n\tGL.create({ canvas: this.bgcanvas });\n\tthis.bgctx = enableWebGLCanvas( this.bgcanvas );\n\twindow.gl = this.gl;\n\t*/\n    };\n\n    /**\n     * marks as dirty the canvas, this way it will be rendered again\n     *\n     * @class LGraphCanvas\n     * @method setDirty\n     * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes)\n     * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires)\n     */\n    LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) {\n        if (fgcanvas) {\n            this.dirty_canvas = true;\n        }\n        if (bgcanvas) {\n            this.dirty_bgcanvas = true;\n        }\n    };\n\n    /**\n     * Used to attach the canvas in a popup\n     *\n     * @method getCanvasWindow\n     * @return {window} returns the window where the canvas is attached (the DOM root node)\n     */\n    LGraphCanvas.prototype.getCanvasWindow = function() {\n        if (!this.canvas) {\n            return window;\n        }\n        var doc = this.canvas.ownerDocument;\n        return doc.defaultView || doc.parentWindow;\n    };\n\n    /**\n     * starts rendering the content of the canvas when needed\n     *\n     * @method startRendering\n     */\n    LGraphCanvas.prototype.startRendering = function() {\n        if (this.is_rendering) {\n            return;\n        } //already rendering\n\n        this.is_rendering = true;\n        renderFrame.call(this);\n\n        function renderFrame() {\n            if (!this.pause_rendering) {\n                this.draw();\n            }\n\n            var window = this.getCanvasWindow();\n            if (this.is_rendering) {\n                window.requestAnimationFrame(renderFrame.bind(this));\n            }\n        }\n    };\n\n    /**\n     * stops rendering the content of the canvas (to save resources)\n     *\n     * @method stopRendering\n     */\n    LGraphCanvas.prototype.stopRendering = function() {\n        this.is_rendering = false;\n        /*\n\tif(this.rendering_timer_id)\n\t{\n\t\tclearInterval(this.rendering_timer_id);\n\t\tthis.rendering_timer_id = null;\n\t}\n\t*/\n    };\n\n    /* LiteGraphCanvas input */\n\n\t//used to block future mouse events (because of im gui)\n\tLGraphCanvas.prototype.blockClick = function()\n\t{\n\t\tthis.block_click = true;\n\t\tthis.last_mouseclick = 0;\n\t}\n\t\n    LGraphCanvas.prototype.processMouseDown = function(e) {\n    \t\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\t\t\n\t\tif (!this.graph) {\n            return;\n        }\n\n        this.adjustMouseEvent(e);\n\n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document;\n        LGraphCanvas.active_canvas = this;\n        var that = this;\n\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\t//console.log(y,this.viewport);\n\t\t//console.log(\"pointerevents: processMouseDown pointerId:\"+e.pointerId+\" which:\"+e.which+\" isPrimary:\"+e.isPrimary+\" :: x y \"+x+\" \"+y);\n\n\t\tthis.ds.viewport = this.viewport;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\n        //move mouse move event to the window in case it drags outside of the canvas\n\t\tif(!this.options.skip_events)\n\t\t{\n\t\t\tLiteGraph.pointerListenerRemove(this.canvas,\"move\", this._mousemove_callback);\n\t\t\tLiteGraph.pointerListenerAdd(ref_window.document,\"move\", this._mousemove_callback,true); //catch for the entire window\n\t\t\tLiteGraph.pointerListenerAdd(ref_window.document,\"up\", this._mouseup_callback,true);\n\t\t}\n\n\t\tif(!is_inside){\n\t\t\treturn;\n\t\t}\n\n        var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 );\n        var skip_dragging = false;\n        var skip_action = false;\n        var now = LiteGraph.getTime();\n\t\tvar is_primary = (e.isPrimary === undefined || !e.isPrimary);\n        var is_double_click = (now - this.last_mouseclick < 300) && is_primary;\n\t\tthis.mouse[0] = e.clientX;\n\t\tthis.mouse[1] = e.clientY;\n        this.graph_mouse[0] = e.canvasX;\n        this.graph_mouse[1] = e.canvasY;\n\t\tthis.last_click_position = [this.mouse[0],this.mouse[1]];\n\t  \t\n\t  \tif (this.pointer_is_down && is_primary ){\n\t\t  this.pointer_is_double = true;\n\t\t  //console.log(\"pointerevents: pointer_is_double start\");\n\t\t}else{\n\t\t  this.pointer_is_double = false;\n\t\t}\n\t  \tthis.pointer_is_down = true;\n\t  \n\t  \t\n        this.canvas.focus();\n\n        LiteGraph.closeAllContextMenus(ref_window);\n\n        if (this.onMouse)\n\t\t{\n            if (this.onMouse(e) == true)\n                return;\n        }\n\n\t\t//left button mouse / single finger\n        if (e.which == 1 && !this.pointer_is_double)\n\t\t{\n            if (e.ctrlKey)\n\t\t\t{\n                this.dragging_rectangle = new Float32Array(4);\n                this.dragging_rectangle[0] = e.canvasX;\n                this.dragging_rectangle[1] = e.canvasY;\n                this.dragging_rectangle[2] = 1;\n                this.dragging_rectangle[3] = 1;\n                skip_action = true;\n            }\n\n            // clone node ALT dragging\n            if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && node && this.allow_interaction && !skip_action && !this.read_only)\n            {\n                if (cloned = node.clone()){\n                    cloned.pos[0] += 5;\n                    cloned.pos[1] += 5;\n                    this.graph.add(cloned,false,{doCalcSize: false});\n                    node = cloned;\n                    skip_action = true;\n                    if (!block_drag_node) {\n                        if (this.allow_dragnodes) {\n\t\t\t\t\t\t\tthis.graph.beforeChange();\n                            this.node_dragged = node;\n                        }\n                        if (!this.selected_nodes[node.id]) {\n                            this.processNodeSelected(node, e);\n                        }\n                    }\n                }\n            }\n            \n            var clicking_canvas_bg = false;\n\n            //when clicked on top of a node\n            //and it is not interactive\n            if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) {\n                if (!this.live_mode && !node.flags.pinned) {\n                    this.bringToFront(node);\n                } //if it wasn't selected?\n\n                //not dragging mouse to connect two slots\n                if ( this.allow_interaction && !this.connecting_node && !node.flags.collapsed && !this.live_mode ) {\n                    //Search for corner for resize\n                    if ( !skip_action &&\n                        node.resizable !== false &&\n                        isInsideRectangle( e.canvasX,\n                            e.canvasY,\n                            node.pos[0] + node.size[0] - 5,\n                            node.pos[1] + node.size[1] - 5,\n                            10,\n                            10\n                        )\n                    ) {\n\t\t\t\t\t\tthis.graph.beforeChange();\n                        this.resizing_node = node;\n                        this.canvas.style.cursor = \"se-resize\";\n                        skip_action = true;\n                    } else {\n                        //search for outputs\n                        if (node.outputs) {\n                            for ( var i = 0, l = node.outputs.length; i < l; ++i ) {\n                                var output = node.outputs[i];\n                                var link_pos = node.getConnectionPos(false, i);\n                                if (\n                                    isInsideRectangle(\n                                        e.canvasX,\n                                        e.canvasY,\n                                        link_pos[0] - 15,\n                                        link_pos[1] - 10,\n                                        30,\n                                        20\n                                    )\n                                ) {\n                                    this.connecting_node = node;\n                                    this.connecting_output = output;\n                                    this.connecting_output.slot_index = i;\n                                    this.connecting_pos = node.getConnectionPos( false, i );\n                                    this.connecting_slot = i;\n\n                                    if (LiteGraph.shift_click_do_break_link_from){\n                                        if (e.shiftKey) {\n                                            node.disconnectOutput(i);\n                                        }\n                                    }\n\n                                    if (is_double_click) {\n                                        if (node.onOutputDblClick) {\n                                            node.onOutputDblClick(i, e);\n                                        }\n                                    } else {\n                                        if (node.onOutputClick) {\n                                            node.onOutputClick(i, e);\n                                        }\n                                    }\n\n                                    skip_action = true;\n                                    break;\n                                }\n                            }\n                        }\n\n                        //search for inputs\n                        if (node.inputs) {\n                            for ( var i = 0, l = node.inputs.length; i < l; ++i ) {\n                                var input = node.inputs[i];\n                                var link_pos = node.getConnectionPos(true, i);\n                                if (\n                                    isInsideRectangle(\n                                        e.canvasX,\n                                        e.canvasY,\n                                        link_pos[0] - 15,\n                                        link_pos[1] - 10,\n                                        30,\n                                        20\n                                    )\n                                ) {\n                                    if (is_double_click) {\n                                        if (node.onInputDblClick) {\n                                            node.onInputDblClick(i, e);\n                                        }\n                                    } else {\n                                        if (node.onInputClick) {\n                                            node.onInputClick(i, e);\n                                        }\n                                    }\n\n                                    if (input.link !== null) {\n                                        var link_info = this.graph.links[\n                                            input.link\n                                        ]; //before disconnecting\n                                        if (LiteGraph.click_do_break_link_to){\n                                            node.disconnectInput(i);\n                                            this.dirty_bgcanvas = true;\n                                            skip_action = true;\n                                        }else{\n                                            // do same action as has not node ?\n                                        }\n\n                                        if (\n                                            this.allow_reconnect_links ||\n\t\t\t\t\t\t\t\t\t\t\t//this.move_destination_link_without_shift ||\n                                            e.shiftKey\n                                        ) {\n                                            if (!LiteGraph.click_do_break_link_to){\n                                                node.disconnectInput(i);\n                                            }\n                                            this.connecting_node = this.graph._nodes_by_id[\n                                                link_info.origin_id\n                                            ];\n                                            this.connecting_slot =\n                                                link_info.origin_slot;\n                                            this.connecting_output = this.connecting_node.outputs[\n                                                this.connecting_slot\n                                            ];\n                                            this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot );\n                                            \n                                            this.dirty_bgcanvas = true;\n                                            skip_action = true;\n                                        }\n\n                                        \n                                    }else{\n                                        // has not node\n                                    }\n                                    \n                                    if (!skip_action){\n                                        // connect from in to out, from to to from\n                                        this.connecting_node = node;\n                                        this.connecting_input = input;\n                                        this.connecting_input.slot_index = i;\n                                        this.connecting_pos = node.getConnectionPos( true, i );\n                                        this.connecting_slot = i;\n                                        \n                                        this.dirty_bgcanvas = true;\n                                        skip_action = true;\n                                    }\n                                }\n                            }\n                        }\n                    } //not resizing\n                }\n\n                //it wasn't clicked on the links boxes\n                if (!skip_action) {\n                    var block_drag_node = false;\n\t\t\t\t\tvar pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]];\n\n                    //widgets\n                    var widget = this.processNodeWidgets( node, this.graph_mouse, e );\n                    if (widget) {\n                        block_drag_node = true;\n                        this.node_widget = [node, widget];\n                    }\n\n                    //double clicking\n                    if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) {\n                        //double click node\n                        if (node.onDblClick) {\n                            node.onDblClick( e, pos, this );\n                        }\n                        this.processNodeDblClicked(node);\n                        block_drag_node = true;\n                    }\n\n                    //if do not capture mouse\n                    if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) {\n                        block_drag_node = true;\n                    } else {\n\t\t\t\t\t\t//open subgraph button\n\t\t\t\t\t\tif(node.subgraph && !node.skip_subgraph_button)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) {\n\t\t\t\t\t\t\t\tvar that = this;\n\t\t\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\t\t\tthat.openSubgraph(node.subgraph);\n\t\t\t\t\t\t\t\t}, 10);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (this.live_mode) {\n\t\t\t\t\t\t\tclicking_canvas_bg = true;\n\t                        block_drag_node = true;\n\t\t\t\t\t\t}\n                    }\n\n                    if (!block_drag_node) {\n                        if (this.allow_dragnodes) {\n\t\t\t\t\t\t\tthis.graph.beforeChange();\n                            this.node_dragged = node;\n                        }\n                        this.processNodeSelected(node, e);\n                    } else { // double-click\n                        /**\n                         * Don't call the function if the block is already selected.\n                         * Otherwise, it could cause the block to be unselected while its panel is open.\n                         */\n                        if (!node.is_selected) this.processNodeSelected(node, e);\n                    }\n\n                    this.dirty_canvas = true;\n                }\n            } //clicked outside of nodes\n            else {\n\t\t\t\tif (!skip_action){\n\t\t\t\t\t//search for link connector\n\t\t\t\t\tif(!this.read_only) {\n\t\t\t\t\t\tfor (var i = 0; i < this.visible_links.length; ++i) {\n\t\t\t\t\t\t\tvar link = this.visible_links[i];\n\t\t\t\t\t\t\tvar center = link._pos;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!center ||\n\t\t\t\t\t\t\t\te.canvasX < center[0] - 4 ||\n\t\t\t\t\t\t\t\te.canvasX > center[0] + 4 ||\n\t\t\t\t\t\t\t\te.canvasY < center[1] - 4 ||\n\t\t\t\t\t\t\t\te.canvasY > center[1] + 4\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t//link clicked\n\t\t\t\t\t\t\tthis.showLinkMenu(link, e);\n\t\t\t\t\t\t\tthis.over_link_center = null; //clear tooltip\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY );\n\t\t\t\t\tthis.selected_group_resizing = false;\n\t\t\t\t\tif (this.selected_group && !this.read_only ) {\n\t\t\t\t\t\tif (e.ctrlKey) {\n\t\t\t\t\t\t\tthis.dragging_rectangle = null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] );\n\t\t\t\t\t\tif (dist * this.ds.scale < 10) {\n\t\t\t\t\t\t\tthis.selected_group_resizing = true;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.selected_group.recomputeInsideNodes();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (is_double_click && !this.read_only && this.allow_searchbox) {\n\t\t\t\t\t\tthis.showSearchBox(e);\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t}\n\n\t\t\t\t\tclicking_canvas_bg = true;\n\t\t\t\t}\n            }\n\n            if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) {\n            \t//console.log(\"pointerevents: dragging_canvas start\");\n            \tthis.dragging_canvas = true;\n            }\n            \n        } else if (e.which == 2) {\n            //middle button\n        \t\n\t\t\tif (LiteGraph.middle_click_slot_add_default_node){\n\t\t\t\tif (node && this.allow_interaction && !skip_action && !this.read_only){\n\t\t\t\t\t//not dragging mouse to connect two slots\n\t\t\t\t\tif (\n\t\t\t\t\t\t!this.connecting_node &&\n\t\t\t\t\t\t!node.flags.collapsed &&\n\t\t\t\t\t\t!this.live_mode\n\t\t\t\t\t) {\n\t\t\t\t\t\tvar mClikSlot = false;\n\t\t\t\t\t\tvar mClikSlot_index = false;\n\t\t\t\t\t\tvar mClikSlot_isOut = false;\n\t\t\t\t\t\t//search for outputs\n\t\t\t\t\t\tif (node.outputs) {\n\t\t\t\t\t\t\tfor ( var i = 0, l = node.outputs.length; i < l; ++i ) {\n\t\t\t\t\t\t\t\tvar output = node.outputs[i];\n\t\t\t\t\t\t\t\tvar link_pos = node.getConnectionPos(false, i);\n\t\t\t\t\t\t\t\tif (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) {\n\t\t\t\t\t\t\t\t\tmClikSlot = output;\n\t\t\t\t\t\t\t\t\tmClikSlot_index = i;\n\t\t\t\t\t\t\t\t\tmClikSlot_isOut = true;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t//search for inputs\n\t\t\t\t\t\tif (node.inputs) {\n\t\t\t\t\t\t\tfor ( var i = 0, l = node.inputs.length; i < l; ++i ) {\n\t\t\t\t\t\t\t\tvar input = node.inputs[i];\n\t\t\t\t\t\t\t\tvar link_pos = node.getConnectionPos(true, i);\n\t\t\t\t\t\t\t\tif (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) {\n\t\t\t\t\t\t\t\t\tmClikSlot = input;\n\t\t\t\t\t\t\t\t\tmClikSlot_index = i;\n\t\t\t\t\t\t\t\t\tmClikSlot_isOut = false;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//console.log(\"middleClickSlots? \"+mClikSlot+\" & \"+(mClikSlot_index!==false));\n\t\t\t\t\t\tif (mClikSlot && mClikSlot_index!==false){\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tvar alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length)));\n\t\t\t\t\t\t\tvar node_bounding = node.getBounding();\n\t\t\t\t\t\t\t// estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes\n\t\t\t\t\t\t\tvar posRef = [\t(!mClikSlot_isOut?node_bounding[0]:node_bounding[0]+node_bounding[2])// + node_bounding[0]/this.canvas.width*150\n\t\t\t\t\t\t\t\t\t\t\t,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical \"derive\"\n\t\t\t\t\t\t\t\t\t\t  ];\n\t\t\t\t\t\t\tvar nodeCreated = this.createDefaultNodeForSlot({   \tnodeFrom: !mClikSlot_isOut?null:node\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotFrom: !mClikSlot_isOut?null:mClikSlot_index\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeTo: !mClikSlot_isOut?node:null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotTo: !mClikSlot_isOut?mClikSlot_index:null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,position: posRef //,e: e\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: \"AUTO\" //nodeNewType\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,posAdd:[!mClikSlot_isOut?-30:30, -alphaPosY*130] //-alphaPosY*30]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,posSizeFix:[!mClikSlot_isOut?-1:0, 0] //-alphaPosY*2*/\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (!skip_action && this.allow_dragcanvas) {\n            \t//console.log(\"pointerevents: dragging_canvas start from middle button\");\n            \tthis.dragging_canvas = true;\n            }\n\n        \t\n        } else if (e.which == 3 || this.pointer_is_double) {\n\t\t\t\n            //right button\n\t\t\tif (this.allow_interaction && !skip_action && !this.read_only){\n\t\t\t\t\n\t\t\t\t// is it hover a node ?\n\t\t\t\tif (node){\n\t\t\t\t\tif(Object.keys(this.selected_nodes).length\n\t\t\t\t\t   && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey)\n\t\t\t\t\t){\n\t\t\t\t\t\t// is multiselected or using shift to include the now node\n\t\t\t\t\t\tif (!this.selected_nodes[node.id]) this.selectNodes([node],true); // add this if not present\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// update selection\n\t\t\t\t\t\tthis.selectNodes([node]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// show menu on this node\n\t\t\t\tthis.processContextMenu(node, e);\n\t\t\t}\n\t\t\t\n        }\n\n        //TODO\n        //if(this.node_selected != prev_selected)\n        //\tthis.onNodeSelectionChange(this.node_selected);\n\n        this.last_mouse[0] = e.clientX;\n        this.last_mouse[1] = e.clientY;\n        this.last_mouseclick = LiteGraph.getTime();\n        this.last_mouse_dragging = true;\n\n        /*\n\tif( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)\n\t\tthis.draw();\n\t*/\n\n        this.graph.change();\n\n        //this is to ensure to defocus(blur) if a text input element is on focus\n        if (\n            !ref_window.document.activeElement ||\n            (ref_window.document.activeElement.nodeName.toLowerCase() !=\n                \"input\" &&\n                ref_window.document.activeElement.nodeName.toLowerCase() !=\n                    \"textarea\")\n        ) {\n            e.preventDefault();\n        }\n        e.stopPropagation();\n\n        if (this.onMouseDown) {\n            this.onMouseDown(e);\n        }\n\n        return false;\n    };\n\n    /**\n     * Called when a mouse move event has to be processed\n     * @method processMouseMove\n     **/\n    LGraphCanvas.prototype.processMouseMove = function(e) {\n        if (this.autoresize) {\n            this.resize();\n        }\n\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\n        if (!this.graph) {\n            return;\n        }\n\n        LGraphCanvas.active_canvas = this;\n        this.adjustMouseEvent(e);\n        var mouse = [e.clientX, e.clientY];\n\t\tthis.mouse[0] = mouse[0];\n\t\tthis.mouse[1] = mouse[1];\n        var delta = [\n            mouse[0] - this.last_mouse[0],\n            mouse[1] - this.last_mouse[1]\n        ];\n        this.last_mouse = mouse;\n        this.graph_mouse[0] = e.canvasX;\n        this.graph_mouse[1] = e.canvasY;\n\n        //console.log(\"pointerevents: processMouseMove \"+e.pointerId+\" \"+e.isPrimary);\n        \n\t\tif(this.block_click)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseMove block_click\");\n\t\t\te.preventDefault();\n\t\t\treturn false;\n\t\t}\n\n        e.dragging = this.last_mouse_dragging;\n\n        if (this.node_widget) {\n            this.processNodeWidgets(\n                this.node_widget[0],\n                this.graph_mouse,\n                e,\n                this.node_widget[1]\n            );\n            this.dirty_canvas = true;\n        }\n\n        //get node over\n        var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes);\n\n        if (this.dragging_rectangle)\n\t\t{\n            this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0];\n            this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1];\n            this.dirty_canvas = true;\n        } \n\t\telse if (this.selected_group && !this.read_only)\n\t\t{\n            //moving/resizing a group\n            if (this.selected_group_resizing) {\n                this.selected_group.size = [\n                    e.canvasX - this.selected_group.pos[0],\n                    e.canvasY - this.selected_group.pos[1]\n                ];\n            } else {\n                var deltax = delta[0] / this.ds.scale;\n                var deltay = delta[1] / this.ds.scale;\n                this.selected_group.move(deltax, deltay, e.ctrlKey);\n                if (this.selected_group._nodes.length) {\n                    this.dirty_canvas = true;\n                }\n            }\n            this.dirty_bgcanvas = true;\n        } else if (this.dragging_canvas) {\n        \t////console.log(\"pointerevents: processMouseMove is dragging_canvas\");\n            this.ds.offset[0] += delta[0] / this.ds.scale;\n            this.ds.offset[1] += delta[1] / this.ds.scale;\n            this.dirty_canvas = true;\n            this.dirty_bgcanvas = true;\n        } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) {\n            if (this.connecting_node) {\n                this.dirty_canvas = true;\n            }\n\n            //remove mouseover flag\n            for (var i = 0, l = this.graph._nodes.length; i < l; ++i) {\n                if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) {\n                    //mouse leave\n                    this.graph._nodes[i].mouseOver = false;\n                    if (this.node_over && this.node_over.onMouseLeave) {\n                        this.node_over.onMouseLeave(e);\n                    }\n                    this.node_over = null;\n                    this.dirty_canvas = true;\n                }\n            }\n\n            //mouse over a node\n            if (node) {\n\n\t\t\t\tif(node.redraw_on_mouse)\n                    this.dirty_canvas = true;\n\n                //this.canvas.style.cursor = \"move\";\n                if (!node.mouseOver) {\n                    //mouse enter\n                    node.mouseOver = true;\n                    this.node_over = node;\n                    this.dirty_canvas = true;\n\n                    if (node.onMouseEnter) {\n                        node.onMouseEnter(e);\n                    }\n                }\n\n                //in case the node wants to do something\n                if (node.onMouseMove) {\n                    node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this );\n                }\n\n                //if dragging a link\n                if (this.connecting_node) {\n                    \n                    if (this.connecting_output){\n                        \n                        var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput\n\n                        //on top of input\n                        if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {\n                            //mouse on top of the corner box, don't know what to do\n                        } else {\n                            //check if I have a slot below de mouse\n                            var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos );\n                            if (slot != -1 && node.inputs[slot]) {\n                                var slot_type = node.inputs[slot].type;\n                                if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) {\n                                    this._highlight_input = pos;\n\t\t\t\t\t\t\t\t\tthis._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS\n                                }\n                            } else {\n                                this._highlight_input = null;\n\t\t\t\t\t\t\t\tthis._highlight_input_slot = null;  // XXX CHECK THIS\n                            }\n                        }\n                        \n                    }else if(this.connecting_input){\n                        \n                        var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput\n\n                        //on top of output\n                        if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {\n                            //mouse on top of the corner box, don't know what to do\n                        } else {\n                            //check if I have a slot below de mouse\n                            var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos );\n                            if (slot != -1 && node.outputs[slot]) {\n                                var slot_type = node.outputs[slot].type;\n                                if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) {\n                                    this._highlight_output = pos;\n                                }\n                            } else {\n                                this._highlight_output = null;\n                            }\n                        }\n                    }\n                }\n\n                //Search for corner\n                if (this.canvas) {\n                    if (\n                        isInsideRectangle(\n                            e.canvasX,\n                            e.canvasY,\n                            node.pos[0] + node.size[0] - 5,\n                            node.pos[1] + node.size[1] - 5,\n                            5,\n                            5\n                        )\n                    ) {\n                        this.canvas.style.cursor = \"se-resize\";\n                    } else {\n                        this.canvas.style.cursor = \"crosshair\";\n                    }\n                }\n            } else { //not over a node\n\n                //search for link connector\n\t\t\t\tvar over_link = null;\n\t\t\t\tfor (var i = 0; i < this.visible_links.length; ++i) {\n\t\t\t\t\tvar link = this.visible_links[i];\n\t\t\t\t\tvar center = link._pos;\n\t\t\t\t\tif (\n\t\t\t\t\t\t!center ||\n\t\t\t\t\t\te.canvasX < center[0] - 4 ||\n\t\t\t\t\t\te.canvasX > center[0] + 4 ||\n\t\t\t\t\t\te.canvasY < center[1] - 4 ||\n\t\t\t\t\t\te.canvasY > center[1] + 4\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tover_link = link;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif( over_link != this.over_link_center )\n\t\t\t\t{\n\t\t\t\t\tthis.over_link_center = over_link;\n\t                this.dirty_canvas = true;\n\t\t\t\t}\n\n\t\t\t\tif (this.canvas) {\n\t                this.canvas.style.cursor = \"\";\n\t\t\t\t}\n\t\t\t} //end\n\n\t\t\t//send event to node if capturing input (used with widgets that allow drag outside of the area of the node)\n            if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) {\n                this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this);\n            }\n\n\t\t\t//node being dragged\n            if (this.node_dragged && !this.live_mode) {\n\t\t\t\t//console.log(\"draggin!\",this.selected_nodes);\n                for (var i in this.selected_nodes) {\n                    var n = this.selected_nodes[i];\n                    n.pos[0] += delta[0] / this.ds.scale;\n                    n.pos[1] += delta[1] / this.ds.scale;\n                    if (!n.is_selected) this.processNodeSelected(n, e); /*\n                     * Don't call the function if the block is already selected.\n                     * Otherwise, it could cause the block to be unselected while dragging.\n                     */\n                }\n\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n            }\n\n            if (this.resizing_node && !this.live_mode) {\n                //convert mouse to node space\n\t\t\t\tvar desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ];\n\t\t\t\tvar min_size = this.resizing_node.computeSize();\n\t\t\t\tdesired_size[0] = Math.max( min_size[0], desired_size[0] );\n\t\t\t\tdesired_size[1] = Math.max( min_size[1], desired_size[1] );\n\t\t\t\tthis.resizing_node.setSize( desired_size );\n\n                this.canvas.style.cursor = \"se-resize\";\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n            }\n        }\n\n        e.preventDefault();\n        return false;\n    };\n\n    /**\n     * Called when a mouse up event has to be processed\n     * @method processMouseUp\n     **/\n    LGraphCanvas.prototype.processMouseUp = function(e) {\n\n\t\tvar is_primary = ( e.isPrimary === undefined || e.isPrimary );\n\n    \t//early exit for extra pointer\n    \tif(!is_primary){\n    \t\t/*e.stopPropagation();\n        \te.preventDefault();*/\n    \t\t//console.log(\"pointerevents: processMouseUp pointerN_stop \"+e.pointerId+\" \"+e.isPrimary);\n    \t\treturn false;\n    \t}\n    \t\n    \t//console.log(\"pointerevents: processMouseUp \"+e.pointerId+\" \"+e.isPrimary+\" :: \"+e.clientX+\" \"+e.clientY);\n    \t\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\n        if (!this.graph)\n            return;\n\n        var window = this.getCanvasWindow();\n        var document = window.document;\n        LGraphCanvas.active_canvas = this;\n\n        //restore the mousemove event back to the canvas\n\t\tif(!this.options.skip_events)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseUp adjustEventListener\");\n\t\t\tLiteGraph.pointerListenerRemove(document,\"move\", this._mousemove_callback,true);\n\t\t\tLiteGraph.pointerListenerAdd(this.canvas,\"move\", this._mousemove_callback,true);\n\t\t\tLiteGraph.pointerListenerRemove(document,\"up\", this._mouseup_callback,true);\n\t\t}\n\n        this.adjustMouseEvent(e);\n        var now = LiteGraph.getTime();\n        e.click_time = now - this.last_mouseclick;\n        this.last_mouse_dragging = false;\n\t\tthis.last_click_position = null;\n\n\t\tif(this.block_click)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseUp block_clicks\");\n\t\t\tthis.block_click = false; //used to avoid sending twice a click in a immediate button\n\t\t}\n\n\t\t//console.log(\"pointerevents: processMouseUp which: \"+e.which);\n\t\t\n        if (e.which == 1) {\n\n\t\t\tif( this.node_widget )\n\t\t\t{\n\t\t\t\tthis.processNodeWidgets( this.node_widget[0], this.graph_mouse, e );\n\t\t\t}\n\n            //left button\n            this.node_widget = null;\n\n            if (this.selected_group) {\n                var diffx =\n                    this.selected_group.pos[0] -\n                    Math.round(this.selected_group.pos[0]);\n                var diffy =\n                    this.selected_group.pos[1] -\n                    Math.round(this.selected_group.pos[1]);\n                this.selected_group.move(diffx, diffy, e.ctrlKey);\n                this.selected_group.pos[0] = Math.round(\n                    this.selected_group.pos[0]\n                );\n                this.selected_group.pos[1] = Math.round(\n                    this.selected_group.pos[1]\n                );\n                if (this.selected_group._nodes.length) {\n                    this.dirty_canvas = true;\n                }\n                this.selected_group = null;\n            }\n            this.selected_group_resizing = false;\n\n\t\t\tvar node = this.graph.getNodeOnPos(\n\t\t\t\t\t\t\te.canvasX,\n\t\t\t\t\t\t\te.canvasY,\n\t\t\t\t\t\t\tthis.visible_nodes\n\t\t\t\t\t\t);\n\t\t\t\n            if (this.dragging_rectangle) {\n                if (this.graph) {\n                    var nodes = this.graph._nodes;\n                    var node_bounding = new Float32Array(4);\n                    \n                    //compute bounding and flip if left to right\n                    var w = Math.abs(this.dragging_rectangle[2]);\n                    var h = Math.abs(this.dragging_rectangle[3]);\n                    var startx =\n                        this.dragging_rectangle[2] < 0\n                            ? this.dragging_rectangle[0] - w\n                            : this.dragging_rectangle[0];\n                    var starty =\n                        this.dragging_rectangle[3] < 0\n                            ? this.dragging_rectangle[1] - h\n                            : this.dragging_rectangle[1];\n                    this.dragging_rectangle[0] = startx;\n                    this.dragging_rectangle[1] = starty;\n                    this.dragging_rectangle[2] = w;\n                    this.dragging_rectangle[3] = h;\n\n\t\t\t\t\t// test dragging rect size, if minimun simulate a click\n\t\t\t\t\tif (!node || (w > 10 && h > 10 )){\n\t\t\t\t\t\t//test against all nodes (not visible because the rectangle maybe start outside\n\t\t\t\t\t\tvar to_select = [];\n\t\t\t\t\t\tfor (var i = 0; i < nodes.length; ++i) {\n\t\t\t\t\t\t\tvar nodeX = nodes[i];\n\t\t\t\t\t\t\tnodeX.getBounding(node_bounding);\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!overlapBounding(\n\t\t\t\t\t\t\t\t\tthis.dragging_rectangle,\n\t\t\t\t\t\t\t\t\tnode_bounding\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t} //out of the visible area\n\t\t\t\t\t\t\tto_select.push(nodeX);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (to_select.length) {\n\t\t\t\t\t\t\tthis.selectNodes(to_select,e.shiftKey); // add to selection with shift\n\t\t\t\t\t\t}\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// will select of update selection\n\t\t\t\t\t\tthis.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey\n\t\t\t\t\t}\n\t\t\t\t\t\n                }\n                this.dragging_rectangle = null;\n            } else if (this.connecting_node) {\n                //dragging a connection\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n\n                var connInOrOut = this.connecting_output || this.connecting_input;\n                var connType = connInOrOut.type;\n                \n                //node below mouse\n                if (node) {\n                    \n                    /* no need to condition on event type.. just another type\n                    if (\n                        connType == LiteGraph.EVENT &&\n                        this.isOverNodeBox(node, e.canvasX, e.canvasY)\n                    ) {\n                        \n                        this.connecting_node.connect(\n                            this.connecting_slot,\n                            node,\n                            LiteGraph.EVENT\n                        );\n                        \n                    } else {*/\n                        \n                        //slot below mouse? connect\n                        \n                        if (this.connecting_output){\n                            \n                            var slot = this.isOverNodeInput(\n                                node,\n                                e.canvasX,\n                                e.canvasY\n                            );\n                            if (slot != -1) {\n                                this.connecting_node.connect(this.connecting_slot, node, slot);\n                            } else {\n                                //not on top of an input\n                                // look for a good slot\n                                this.connecting_node.connectByType(this.connecting_slot,node,connType);\n                            }\n                            \n                        }else if (this.connecting_input){\n                            \n                            var slot = this.isOverNodeOutput(\n                                node,\n                                e.canvasX,\n                                e.canvasY\n                            );\n\n                            if (slot != -1) {\n                                node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like\n                            } else {\n                                //not on top of an input\n                                // look for a good slot\n                                this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType);\n                            }\n                            \n                        }\n                        \n                        \n                    //}\n                    \n                }else{\n                    \n                    // add menu when releasing link in empty space\n                \tif (LiteGraph.release_link_on_empty_shows_menu){\n\t                    if (e.shiftKey && this.allow_searchbox){\n\t                        if(this.connecting_output){\n\t                            this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type});\n\t                        }else if(this.connecting_input){\n\t                            this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type});\n\t                        }\n\t                    }else{\n\t                        if(this.connecting_output){\n\t                            this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e});\n\t                        }else if(this.connecting_input){\n\t                            this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e});\n\t                        }\n\t                    }\n                \t}\n                }\n\n                this.connecting_output = null;\n                this.connecting_input = null;\n                this.connecting_pos = null;\n                this.connecting_node = null;\n                this.connecting_slot = -1;\n            } //not dragging connection\n            else if (this.resizing_node) {\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n\t\t\t\tthis.graph.afterChange(this.resizing_node);\n                this.resizing_node = null;\n            } else if (this.node_dragged) {\n                //node being dragged?\n                var node = this.node_dragged;\n                if (\n                    node &&\n                    e.click_time < 300 &&\n                    isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT )\n                ) {\n                    node.collapse();\n                }\n\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n                this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]);\n                this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]);\n                if (this.graph.config.align_to_grid || this.align_to_grid ) {\n                    this.node_dragged.alignToGrid();\n                }\n\t\t\t\tif( this.onNodeMoved )\n\t\t\t\t\tthis.onNodeMoved( this.node_dragged );\n\t\t\t\tthis.graph.afterChange(this.node_dragged);\n                this.node_dragged = null;\n            } //no node being dragged\n            else {\n                //get node over\n                var node = this.graph.getNodeOnPos(\n                    e.canvasX,\n                    e.canvasY,\n                    this.visible_nodes\n                );\n\n                if (!node && e.click_time < 300) {\n                    this.deselectAllNodes();\n                }\n\n                this.dirty_canvas = true;\n                this.dragging_canvas = false;\n\n                if (this.node_over && this.node_over.onMouseUp) {\n                    this.node_over.onMouseUp( e, [ e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1] ], this );\n                }\n                if (\n                    this.node_capturing_input &&\n                    this.node_capturing_input.onMouseUp\n                ) {\n                    this.node_capturing_input.onMouseUp(e, [\n                        e.canvasX - this.node_capturing_input.pos[0],\n                        e.canvasY - this.node_capturing_input.pos[1]\n                    ]);\n                }\n            }\n        } else if (e.which == 2) {\n            //middle button\n            //trace(\"middle\");\n            this.dirty_canvas = true;\n            this.dragging_canvas = false;\n        } else if (e.which == 3) {\n            //right button\n            //trace(\"right\");\n            this.dirty_canvas = true;\n            this.dragging_canvas = false;\n        }\n\n        /*\n\t\tif((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)\n\t\t\tthis.draw();\n\t\t*/\n\n\t  \tif (is_primary)\n\t\t{\n\t\t\tthis.pointer_is_down = false;\n\t\t\tthis.pointer_is_double = false;\n\t\t}\n\t  \n        this.graph.change();\n\n        //console.log(\"pointerevents: processMouseUp stopPropagation\");\n        e.stopPropagation();\n        e.preventDefault();\n        return false;\n    };\n\n    /**\n     * Called when a mouse wheel event has to be processed\n     * @method processMouseWheel\n     **/\n    LGraphCanvas.prototype.processMouseWheel = function(e) {\n        if (!this.graph || !this.allow_dragcanvas) {\n            return;\n        }\n\n        var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60;\n\n        this.adjustMouseEvent(e);\n\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\t\tif(!is_inside)\n\t\t\treturn;\n\n        var scale = this.ds.scale;\n\n        if (delta > 0) {\n            scale *= 1.1;\n        } else if (delta < 0) {\n            scale *= 1 / 1.1;\n        }\n\n        //this.setZoom( scale, [ e.clientX, e.clientY ] );\n        this.ds.changeScale(scale, [e.clientX, e.clientY]);\n\n        this.graph.change();\n\n        e.preventDefault();\n        return false; // prevent default\n    };\n\n    /**\n     * returns true if a position (in graph space) is on top of a node little corner box\n     * @method isOverNodeBox\n     **/\n    LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) {\n        var title_height = LiteGraph.NODE_TITLE_HEIGHT;\n        if (\n            isInsideRectangle(\n                canvasx,\n                canvasy,\n                node.pos[0] + 2,\n                node.pos[1] + 2 - title_height,\n                title_height - 4,\n                title_height - 4\n            )\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    /**\n     * returns the INDEX if a position (in graph space) is on top of a node input slot\n     * @method isOverNodeInput\n     **/\n    LGraphCanvas.prototype.isOverNodeInput = function(\n        node,\n        canvasx,\n        canvasy,\n        slot_pos\n    ) {\n        if (node.inputs) {\n            for (var i = 0, l = node.inputs.length; i < l; ++i) {\n                var input = node.inputs[i];\n                var link_pos = node.getConnectionPos(true, i);\n                var is_inside = false;\n                if (node.horizontal) {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 5,\n                        link_pos[1] - 10,\n                        10,\n                        20\n                    );\n                } else {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        40,\n                        10\n                    );\n                }\n                if (is_inside) {\n                    if (slot_pos) {\n                        slot_pos[0] = link_pos[0];\n                        slot_pos[1] = link_pos[1];\n                    }\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n    \n    /**\n     * returns the INDEX if a position (in graph space) is on top of a node output slot\n     * @method isOverNodeOuput\n     **/\n    LGraphCanvas.prototype.isOverNodeOutput = function(\n        node,\n        canvasx,\n        canvasy,\n        slot_pos\n    ) {\n        if (node.outputs) {\n            for (var i = 0, l = node.outputs.length; i < l; ++i) {\n                var output = node.outputs[i];\n                var link_pos = node.getConnectionPos(false, i);\n                var is_inside = false;\n                if (node.horizontal) {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 5,\n                        link_pos[1] - 10,\n                        10,\n                        20\n                    );\n                } else {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        40,\n                        10\n                    );\n                }\n                if (is_inside) {\n                    if (slot_pos) {\n                        slot_pos[0] = link_pos[0];\n                        slot_pos[1] = link_pos[1];\n                    }\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * process a key event\n     * @method processKey\n     **/\n    LGraphCanvas.prototype.processKey = function(e) {\n        if (!this.graph) {\n            return;\n        }\n\n        var block_default = false;\n        //console.log(e); //debug\n\n        if (e.target.localName == \"input\") {\n            return;\n        }\n\n        if (e.type == \"keydown\") {\n            if (e.keyCode == 32) {\n                //space\n                this.dragging_canvas = true;\n                block_default = true;\n            }\n            \n            if (e.keyCode == 27) {\n                //esc\n                if(this.node_panel) this.node_panel.close();\n                if(this.options_panel) this.options_panel.close();\n                block_default = true;\n            }\n\n            //select all Control A\n            if (e.keyCode == 65 && e.ctrlKey) {\n                this.selectNodes();\n                block_default = true;\n            }\n\n            if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) {\n                //copy\n                if (this.selected_nodes) {\n                    this.copyToClipboard();\n                    block_default = true;\n                }\n            }\n\n            if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) {\n                //paste\n                this.pasteFromClipboard(e.shiftKey);\n            }\n\n            //delete or backspace\n            if (e.keyCode == 46 || e.keyCode == 8) {\n                if (\n                    e.target.localName != \"input\" &&\n                    e.target.localName != \"textarea\"\n                ) {\n                    this.deleteSelectedNodes();\n                    block_default = true;\n                }\n            }\n\n            //collapse\n            //...\n\n            //TODO\n            if (this.selected_nodes) {\n                for (var i in this.selected_nodes) {\n                    if (this.selected_nodes[i].onKeyDown) {\n                        this.selected_nodes[i].onKeyDown(e);\n                    }\n                }\n            }\n        } else if (e.type == \"keyup\") {\n            if (e.keyCode == 32) {\n                // space\n                this.dragging_canvas = false;\n            }\n\n            if (this.selected_nodes) {\n                for (var i in this.selected_nodes) {\n                    if (this.selected_nodes[i].onKeyUp) {\n                        this.selected_nodes[i].onKeyUp(e);\n                    }\n                }\n            }\n        }\n\n        this.graph.change();\n\n        if (block_default) {\n            e.preventDefault();\n            e.stopImmediatePropagation();\n            return false;\n        }\n    };\n\n    LGraphCanvas.prototype.copyToClipboard = function() {\n        var clipboard_info = {\n            nodes: [],\n            links: []\n        };\n        var index = 0;\n        var selected_nodes_array = [];\n        for (var i in this.selected_nodes) {\n            var node = this.selected_nodes[i];\n            if (node.clonable === false)\n                continue;\n            node._relative_id = index;\n            selected_nodes_array.push(node);\n            index += 1;\n        }\n\n        for (var i = 0; i < selected_nodes_array.length; ++i) {\n            var node = selected_nodes_array[i];\n            if(node.clonable === false)\n                continue;\n            var cloned = node.clone();\n            if(!cloned)\n            {\n                console.warn(\"node type not found: \" + node.type );\n                continue;\n            }\n            clipboard_info.nodes.push(cloned.serialize());\n            if (node.inputs && node.inputs.length) {\n                for (var j = 0; j < node.inputs.length; ++j) {\n                    var input = node.inputs[j];\n                    if (!input || input.link == null) {\n                        continue;\n                    }\n                    var link_info = this.graph.links[input.link];\n                    if (!link_info) {\n                        continue;\n                    }\n                    var target_node = this.graph.getNodeById(\n                        link_info.origin_id\n                    );\n                    if (!target_node) {\n                        continue;\n                    }\n                    clipboard_info.links.push([\n                        target_node._relative_id,\n                        link_info.origin_slot, //j,\n                        node._relative_id,\n                        link_info.target_slot,\n                        target_node.id\n                    ]);\n                }\n            }\n        }\n        localStorage.setItem(\n            \"litegrapheditor_clipboard\",\n            JSON.stringify(clipboard_info)\n        );\n    };\n\n    LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) {\n        // if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior\n        if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {\n            return;\n        }\n        var data = localStorage.getItem(\"litegrapheditor_clipboard\");\n        if (!data) {\n            return;\n        }\n\n\t\tthis.graph.beforeChange();\n\n        //create nodes\n        var clipboard_info = JSON.parse(data);\n        // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos\n        var posMin = false;\n        var posMinIndexes = false;\n        for (var i = 0; i < clipboard_info.nodes.length; ++i) {\n            if (posMin){\n                if(posMin[0]>clipboard_info.nodes[i].pos[0]){\n                    posMin[0] = clipboard_info.nodes[i].pos[0];\n                    posMinIndexes[0] = i;\n                }\n                if(posMin[1]>clipboard_info.nodes[i].pos[1]){\n                    posMin[1] = clipboard_info.nodes[i].pos[1];\n                    posMinIndexes[1] = i;\n                }\n            }\n            else{\n                posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]];\n                posMinIndexes = [i, i];\n            }\n        }\n        var nodes = [];\n        for (var i = 0; i < clipboard_info.nodes.length; ++i) {\n            var node_data = clipboard_info.nodes[i];\n            var node = LiteGraph.createNode(node_data.type);\n            if (node) {\n                node.configure(node_data);\n        \n\t\t\t\t//paste in last known mouse position\n                node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5;\n                node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5;\n\n                this.graph.add(node,{doProcessChange:false});\n                \n                nodes.push(node);\n            }\n        }\n\n        //create links\n        for (var i = 0; i < clipboard_info.links.length; ++i) {\n            var link_info = clipboard_info.links[i];\n            var origin_node;\n            var origin_node_relative_id = link_info[0];\n            if (origin_node_relative_id != null) {\n                origin_node = nodes[origin_node_relative_id];\n            } else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {\n                var origin_node_id = link_info[4];\n                if (origin_node_id) {\n                    origin_node = this.graph.getNodeById(origin_node_id);\n                }\n            }\n            var target_node = nodes[link_info[2]];\n\t\t\tif( origin_node && target_node )\n\t            origin_node.connect(link_info[1], target_node, link_info[3]);\n\t\t\telse\n\t\t\t\tconsole.warn(\"Warning, nodes missing on pasting\");\n        }\n\n        this.selectNodes(nodes);\n\n\t\tthis.graph.afterChange();\n    };\n\n    /**\n     * process a item drop event on top the canvas\n     * @method processDrop\n     **/\n    LGraphCanvas.prototype.processDrop = function(e) {\n        e.preventDefault();\n        this.adjustMouseEvent(e);\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\t\tif(!is_inside){\n\t\t\treturn;\n\t\t\t// --- BREAK ---\n\t\t}\n\n        var pos = [e.canvasX, e.canvasY];\n\n\n        var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null;\n\n        if (!node) {\n            var r = null;\n            if (this.onDropItem) {\n                r = this.onDropItem(event);\n            }\n            if (!r) {\n                this.checkDropItem(e);\n            }\n            return;\n        }\n\n        if (node.onDropFile || node.onDropData) {\n            var files = e.dataTransfer.files;\n            if (files && files.length) {\n                for (var i = 0; i < files.length; i++) {\n                    var file = e.dataTransfer.files[0];\n                    var filename = file.name;\n                    var ext = LGraphCanvas.getFileExtension(filename);\n                    //console.log(file);\n\n                    if (node.onDropFile) {\n                        node.onDropFile(file);\n                    }\n\n                    if (node.onDropData) {\n                        //prepare reader\n                        var reader = new FileReader();\n                        reader.onload = function(event) {\n                            //console.log(event.target);\n                            var data = event.target.result;\n                            node.onDropData(data, filename, file);\n                        };\n\n                        //read data\n                        var type = file.type.split(\"/\")[0];\n                        if (type == \"text\" || type == \"\") {\n                            reader.readAsText(file);\n                        } else if (type == \"image\") {\n                            reader.readAsDataURL(file);\n                        } else {\n                            reader.readAsArrayBuffer(file);\n                        }\n                    }\n                }\n            }\n        }\n\n        if (node.onDropItem) {\n            if (node.onDropItem(event)) {\n                return true;\n            }\n        }\n\n        if (this.onDropItem) {\n            return this.onDropItem(event);\n        }\n\n        return false;\n    };\n\n    //called if the graph doesn't have a default drop item behaviour\n    LGraphCanvas.prototype.checkDropItem = function(e) {\n        if (e.dataTransfer.files.length) {\n            var file = e.dataTransfer.files[0];\n            var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase();\n            var nodetype = LiteGraph.node_types_by_file_extension[ext];\n            if (nodetype) {\n\t\t\t\tthis.graph.beforeChange();\n                var node = LiteGraph.createNode(nodetype.type);\n                node.pos = [e.canvasX, e.canvasY];\n                this.graph.add(node);\n                if (node.onDropFile) {\n                    node.onDropFile(file);\n                }\n\t\t\t\tthis.graph.afterChange();\n            }\n        }\n    };\n\n    LGraphCanvas.prototype.processNodeDblClicked = function(n) {\n        if (this.onShowNodePanel) {\n            this.onShowNodePanel(n);\n        }\n\t\telse\n\t\t{\n\t\t\tthis.showShowNodePanel(n);\n\t\t}\n\n        if (this.onNodeDblClicked) {\n            this.onNodeDblClicked(n);\n        }\n\n        this.setDirty(true);\n    };\n\n    LGraphCanvas.prototype.processNodeSelected = function(node, e) {\n        this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select));\n        if (this.onNodeSelected) {\n            this.onNodeSelected(node);\n        }\n    };\n\n    /**\n     * selects a given node (or adds it to the current selection)\n     * @method selectNode\n     **/\n    LGraphCanvas.prototype.selectNode = function(\n        node,\n        add_to_current_selection\n    ) {\n        if (node == null) {\n            this.deselectAllNodes();\n        } else {\n            this.selectNodes([node], add_to_current_selection);\n        }\n    };\n\n    /**\n     * selects several nodes (or adds them to the current selection)\n     * @method selectNodes\n     **/\n    LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection )\n\t{\n\t\tif (!add_to_current_selection) {\n            this.deselectAllNodes();\n        }\n\n        nodes = nodes || this.graph._nodes;\n\t\tif (typeof nodes == \"string\") nodes = [nodes];\n        for (var i in nodes) {\n            var node = nodes[i];\n            if (node.is_selected) {\n                this.deselectNode(node);\n                continue;\n            }\n\n            if (!node.is_selected && node.onSelected) {\n                node.onSelected();\n            }\n            node.is_selected = true;\n            this.selected_nodes[node.id] = node;\n\n            if (node.inputs) {\n                for (var j = 0; j < node.inputs.length; ++j) {\n                    this.highlighted_links[node.inputs[j].link] = true;\n                }\n            }\n            if (node.outputs) {\n                for (var j = 0; j < node.outputs.length; ++j) {\n                    var out = node.outputs[j];\n                    if (out.links) {\n                        for (var k = 0; k < out.links.length; ++k) {\n                            this.highlighted_links[out.links[k]] = true;\n                        }\n                    }\n                }\n            }\n        }\n\n\t\tif(\tthis.onSelectionChange )\n\t\t\tthis.onSelectionChange( this.selected_nodes );\n\n        this.setDirty(true);\n    };\n\n    /**\n     * removes a node from the current selection\n     * @method deselectNode\n     **/\n    LGraphCanvas.prototype.deselectNode = function(node) {\n        if (!node.is_selected) {\n            return;\n        }\n        if (node.onDeselected) {\n            node.onDeselected();\n        }\n        node.is_selected = false;\n\n        if (this.onNodeDeselected) {\n            this.onNodeDeselected(node);\n        }\n\n        //remove highlighted\n        if (node.inputs) {\n            for (var i = 0; i < node.inputs.length; ++i) {\n                delete this.highlighted_links[node.inputs[i].link];\n            }\n        }\n        if (node.outputs) {\n            for (var i = 0; i < node.outputs.length; ++i) {\n                var out = node.outputs[i];\n                if (out.links) {\n                    for (var j = 0; j < out.links.length; ++j) {\n                        delete this.highlighted_links[out.links[j]];\n                    }\n                }\n            }\n        }\n    };\n\n    /**\n     * removes all nodes from the current selection\n     * @method deselectAllNodes\n     **/\n    LGraphCanvas.prototype.deselectAllNodes = function() {\n        if (!this.graph) {\n            return;\n        }\n        var nodes = this.graph._nodes;\n        for (var i = 0, l = nodes.length; i < l; ++i) {\n            var node = nodes[i];\n            if (!node.is_selected) {\n                continue;\n            }\n            if (node.onDeselected) {\n                node.onDeselected();\n            }\n            node.is_selected = false;\n\t\t\tif (this.onNodeDeselected) {\n\t\t\t\tthis.onNodeDeselected(node);\n\t\t\t}\n        }\n        this.selected_nodes = {};\n        this.current_node = null;\n        this.highlighted_links = {};\n\t\tif(\tthis.onSelectionChange )\n\t\t\tthis.onSelectionChange( this.selected_nodes );\n        this.setDirty(true);\n    };\n\n    /**\n     * deletes all nodes in the current selection from the graph\n     * @method deleteSelectedNodes\n     **/\n    LGraphCanvas.prototype.deleteSelectedNodes = function() {\n\n\t\tthis.graph.beforeChange();\n\n        for (var i in this.selected_nodes) {\n            var node = this.selected_nodes[i];\n\n\t\t\tif(node.block_delete)\n\t\t\t\tcontinue;\n\n\t\t\t//autoconnect when possible (very basic, only takes into account first input-output)\n\t\t\tif(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) \n\t\t\t{\n\t\t\t\tvar input_link = node.graph.links[ node.inputs[0].link ];\n\t\t\t\tvar output_link = node.graph.links[ node.outputs[0].links[0] ];\n\t\t\t\tvar input_node = node.getInputNode(0);\n\t\t\t\tvar output_node = node.getOutputNodes(0)[0];\n\t\t\t\tif(input_node && output_node)\n\t\t\t\t\tinput_node.connect( input_link.origin_slot, output_node, output_link.target_slot );\n\t\t\t}\n            this.graph.remove(node);\n\t\t\tif (this.onNodeDeselected) {\n\t\t\t\tthis.onNodeDeselected(node);\n\t\t\t}\n        }\n        this.selected_nodes = {};\n        this.current_node = null;\n        this.highlighted_links = {};\n        this.setDirty(true);\n\t\tthis.graph.afterChange();\n    };\n    \n    /**\n     * centers the camera on a given node\n     * @method centerOnNode\n     **/\n    LGraphCanvas.prototype.centerOnNode = function(node) {\n        this.ds.offset[0] =\n            -node.pos[0] -\n            node.size[0] * 0.5 +\n            (this.canvas.width * 0.5) / this.ds.scale;\n        this.ds.offset[1] =\n            -node.pos[1] -\n            node.size[1] * 0.5 +\n            (this.canvas.height * 0.5) / this.ds.scale;\n        this.setDirty(true, true);\n    };\n\n    /**\n     * adds some useful properties to a mouse event, like the position in graph coordinates\n     * @method adjustMouseEvent\n     **/\n    LGraphCanvas.prototype.adjustMouseEvent = function(e) {\n\tvar clientX_rel = 0;\n        var clientY_rel = 0;\n\t    \n    \tif (this.canvas) {\n            var b = this.canvas.getBoundingClientRect();\n            clientX_rel = e.clientX - b.left;\n            clientY_rel = e.clientY - b.top;\n        } else {\n        \tclientX_rel = e.clientX;\n        \tclientY_rel = e.clientY;\n        }\n    \t\n        // e.deltaX = clientX_rel - this.last_mouse_position[0];\n        // e.deltaY = clientY_rel- this.last_mouse_position[1];\n\n        this.last_mouse_position[0] = clientX_rel;\n        this.last_mouse_position[1] = clientY_rel;\n\n        e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0];\n        e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1];\n        \n        //console.log(\"pointerevents: adjustMouseEvent \"+e.clientX+\":\"+e.clientY+\" \"+clientX_rel+\":\"+clientY_rel+\" \"+e.canvasX+\":\"+e.canvasY);\n    };\n\n    /**\n     * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom\n     * @method setZoom\n     **/\n    LGraphCanvas.prototype.setZoom = function(value, zooming_center) {\n        this.ds.changeScale(value, zooming_center);\n        /*\n\tif(!zooming_center && this.canvas)\n\t\tzooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5];\n\n\tvar center = this.convertOffsetToCanvas( zooming_center );\n\n\tthis.ds.scale = value;\n\n\tif(this.scale > this.max_zoom)\n\t\tthis.scale = this.max_zoom;\n\telse if(this.scale < this.min_zoom)\n\t\tthis.scale = this.min_zoom;\n\n\tvar new_center = this.convertOffsetToCanvas( zooming_center );\n\tvar delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];\n\n\tthis.offset[0] += delta_offset[0];\n\tthis.offset[1] += delta_offset[1];\n\t*/\n\n        this.dirty_canvas = true;\n        this.dirty_bgcanvas = true;\n    };\n\n    /**\n     * converts a coordinate from graph coordinates to canvas2D coordinates\n     * @method convertOffsetToCanvas\n     **/\n    LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) {\n        return this.ds.convertOffsetToCanvas(pos, out);\n    };\n\n    /**\n     * converts a coordinate from Canvas2D coordinates to graph space\n     * @method convertCanvasToOffset\n     **/\n    LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) {\n        return this.ds.convertCanvasToOffset(pos, out);\n    };\n\n    //converts event coordinates from canvas2D to graph coordinates\n    LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) {\n        var rect = this.canvas.getBoundingClientRect();\n        return this.convertCanvasToOffset([\n            e.clientX - rect.left,\n            e.clientY - rect.top\n        ]);\n    };\n\n    /**\n     * brings a node to front (above all other nodes)\n     * @method bringToFront\n     **/\n    LGraphCanvas.prototype.bringToFront = function(node) {\n        var i = this.graph._nodes.indexOf(node);\n        if (i == -1) {\n            return;\n        }\n\n        this.graph._nodes.splice(i, 1);\n        this.graph._nodes.push(node);\n    };\n\n    /**\n     * sends a node to the back (below all other nodes)\n     * @method sendToBack\n     **/\n    LGraphCanvas.prototype.sendToBack = function(node) {\n        var i = this.graph._nodes.indexOf(node);\n        if (i == -1) {\n            return;\n        }\n\n        this.graph._nodes.splice(i, 1);\n        this.graph._nodes.unshift(node);\n    };\n\n    /* Interaction */\n\n    /* LGraphCanvas render */\n    var temp = new Float32Array(4);\n\n    /**\n     * checks which nodes are visible (inside the camera area)\n     * @method computeVisibleNodes\n     **/\n    LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) {\n        var visible_nodes = out || [];\n        visible_nodes.length = 0;\n        nodes = nodes || this.graph._nodes;\n        for (var i = 0, l = nodes.length; i < l; ++i) {\n            var n = nodes[i];\n\n            //skip rendering nodes in live mode\n            if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) {\n                continue;\n            }\n\n            if (!overlapBounding(this.visible_area, n.getBounding(temp, true))) {\n                continue;\n            } //out of the visible area\n\n            visible_nodes.push(n);\n        }\n        return visible_nodes;\n    };\n\n    /**\n     * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)\n     * @method draw\n     **/\n    LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) {\n        if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) {\n            return;\n        }\n\n        //fps counting\n        var now = LiteGraph.getTime();\n        this.render_time = (now - this.last_draw_time) * 0.001;\n        this.last_draw_time = now;\n\n        if (this.graph) {\n            this.ds.computeVisibleArea(this.viewport);\n        }\n\n        if (\n            this.dirty_bgcanvas ||\n            force_bgcanvas ||\n            this.always_render_background ||\n            (this.graph &&\n                this.graph._last_trigger_time &&\n                now - this.graph._last_trigger_time < 1000)\n        ) {\n            this.drawBackCanvas();\n        }\n\n        if (this.dirty_canvas || force_canvas) {\n            this.drawFrontCanvas();\n        }\n\n        this.fps = this.render_time ? 1.0 / this.render_time : 0;\n        this.frame += 1;\n    };\n\n    /**\n     * draws the front canvas (the one containing all the nodes)\n     * @method drawFrontCanvas\n     **/\n    LGraphCanvas.prototype.drawFrontCanvas = function() {\n        this.dirty_canvas = false;\n\n        if (!this.ctx) {\n            this.ctx = this.bgcanvas.getContext(\"2d\");\n        }\n        var ctx = this.ctx;\n        if (!ctx) {\n            //maybe is using webgl...\n            return;\n        }\n\n        var canvas = this.canvas;\n        if ( ctx.start2D && !this.viewport ) {\n            ctx.start2D();\n\t\t\tctx.restore();\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n        }\n\n        //clip dirty area if there is one, otherwise work in full canvas\n\t\tvar area = this.viewport || this.dirty_area;\n        if (area) {\n            ctx.save();\n            ctx.beginPath();\n            ctx.rect( area[0],area[1],area[2],area[3] );\n            ctx.clip();\n        }\n\n        //clear\n        //canvas.width = canvas.width;\n        if (this.clear_background) {\n\t\t\tif(area)\n\t            ctx.clearRect( area[0],area[1],area[2],area[3] );\n\t\t\telse\n\t            ctx.clearRect(0, 0, canvas.width, canvas.height);\n        }\n\n        //draw bg canvas\n        if (this.bgcanvas == this.canvas) {\n            this.drawBackCanvas();\n        } else {\n            ctx.drawImage( this.bgcanvas, 0, 0 );\n        }\n\n        //rendering\n        if (this.onRender) {\n            this.onRender(canvas, ctx);\n        }\n\n        //info widget\n        if (this.show_info) {\n            this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0 );\n        }\n\n        if (this.graph) {\n            //apply transformations\n            ctx.save();\n            this.ds.toCanvasContext(ctx);\n\n            //draw nodes\n            var drawn_nodes = 0;\n            var visible_nodes = this.computeVisibleNodes(\n                null,\n                this.visible_nodes\n            );\n\n            for (var i = 0; i < visible_nodes.length; ++i) {\n                var node = visible_nodes[i];\n\n                //transform coords system\n                ctx.save();\n                ctx.translate(node.pos[0], node.pos[1]);\n\n                //Draw\n                this.drawNode(node, ctx);\n                drawn_nodes += 1;\n\n                //Restore\n                ctx.restore();\n            }\n\n            //on top (debug)\n            if (this.render_execution_order) {\n                this.drawExecutionOrder(ctx);\n            }\n\n            //connections ontop?\n            if (this.graph.config.links_ontop) {\n                if (!this.live_mode) {\n                    this.drawConnections(ctx);\n                }\n            }\n\n            //current connection (the one being dragged by the mouse)\n            if (this.connecting_pos != null) {\n                ctx.lineWidth = this.connections_width;\n                var link_color = null;\n                \n                var connInOrOut = this.connecting_output || this.connecting_input;\n\n                var connType = connInOrOut.type;\n                var connDir = connInOrOut.dir;\n\t\t\t\tif(connDir == null)\n\t\t\t\t{\n\t\t\t\t\tif (this.connecting_output)\n\t\t\t\t\t\tconnDir = this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT;\n\t\t\t\t\telse\n\t\t\t\t\t\tconnDir = this.connecting_node.horizontal ? LiteGraph.UP : LiteGraph.LEFT;\n\t\t\t\t}\n                var connShape = connInOrOut.shape;\n                \n                switch (connType) {\n                    case LiteGraph.EVENT:\n                        link_color = LiteGraph.EVENT_LINK_COLOR;\n                        break;\n                    default:\n                        link_color = LiteGraph.CONNECTING_LINK_COLOR;\n                }\n\n                //the connection being dragged by the mouse\n                this.renderLink(\n                    ctx,\n                    this.connecting_pos,\n                    [this.graph_mouse[0], this.graph_mouse[1]],\n                    null,\n                    false,\n                    null,\n                    link_color,\n                    connDir,\n                    LiteGraph.CENTER\n                );\n\n                ctx.beginPath();\n                if (\n                    connType === LiteGraph.EVENT ||\n                    connShape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(\n                        this.connecting_pos[0] - 6 + 0.5,\n                        this.connecting_pos[1] - 5 + 0.5,\n                        14,\n                        10\n                    );\n\t                ctx.fill();\n\t\t\t\t\tctx.beginPath();\n                    ctx.rect(\n                        this.graph_mouse[0] - 6 + 0.5,\n                        this.graph_mouse[1] - 5 + 0.5,\n                        14,\n                        10\n                    );\n                } else if (connShape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5);\n                    ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5);\n                    ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5);\n                    ctx.closePath();\n                } \n                else {\n                    ctx.arc(\n                        this.connecting_pos[0],\n                        this.connecting_pos[1],\n                        4,\n                        0,\n                        Math.PI * 2\n                    );\n\t                ctx.fill();\n\t\t\t\t\tctx.beginPath();\n                    ctx.arc(\n                        this.graph_mouse[0],\n                        this.graph_mouse[1],\n                        4,\n                        0,\n                        Math.PI * 2\n                    );\n                }\n                ctx.fill();\n\n                ctx.fillStyle = \"#ffcc00\";\n                if (this._highlight_input) {\n                    ctx.beginPath();\n                    var shape = this._highlight_input_slot.shape;\n                    if (shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5);\n                        ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5);\n                        ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else {\n                        ctx.arc(\n                            this._highlight_input[0],\n                            this._highlight_input[1],\n                            6,\n                            0,\n                            Math.PI * 2\n                        );\n                    }\n                    ctx.fill();\n                }\n                if (this._highlight_output) {\n                    ctx.beginPath();\n                    if (shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5);\n                        ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5);\n                        ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else {\n                        ctx.arc(\n                            this._highlight_output[0],\n                            this._highlight_output[1],\n                            6,\n                            0,\n                            Math.PI * 2\n                        );\n                    }\n                    ctx.fill();\n                }\n            }\n\n\t\t\t//the selection rectangle\n            if (this.dragging_rectangle) {\n                ctx.strokeStyle = \"#FFF\";\n                ctx.strokeRect(\n                    this.dragging_rectangle[0],\n                    this.dragging_rectangle[1],\n                    this.dragging_rectangle[2],\n                    this.dragging_rectangle[3]\n                );\n            }\n\n\t\t\t//on top of link center\n\t\t\tif(this.over_link_center && this.render_link_tooltip)\n\t\t\t\tthis.drawLinkTooltip( ctx, this.over_link_center );\n\t\t\telse\n\t\t\t\tif(this.onDrawLinkTooltip) //to remove\n\t\t\t\t\tthis.onDrawLinkTooltip(ctx,null);\n\n\t\t\t//custom info\n            if (this.onDrawForeground) {\n                this.onDrawForeground(ctx, this.visible_rect);\n            }\n\n            ctx.restore();\n        }\n\n\t\t//draws panel in the corner \n\t\tif (this._graph_stack && this._graph_stack.length) {\n\t\t\tthis.drawSubgraphPanel( ctx );\n\t\t}\n\n\n        if (this.onDrawOverlay) {\n            this.onDrawOverlay(ctx);\n        }\n\n        if (area){\n            ctx.restore();\n        }\n\n        if (ctx.finish2D) {\n            //this is a function I use in webgl renderer\n            ctx.finish2D();\n        }\n    };\n\n    /**\n     * draws the panel in the corner that shows subgraph properties\n     * @method drawSubgraphPanel\n     **/\n    LGraphCanvas.prototype.drawSubgraphPanel = function (ctx) {\n        var subgraph = this.graph;\n        var subnode = subgraph._subgraph_node;\n        if (!subnode) {\n            console.warn(\"subgraph without subnode\");\n            return;\n        }\n        this.drawSubgraphPanelLeft(subgraph, subnode, ctx)\n        this.drawSubgraphPanelRight(subgraph, subnode, ctx)\n    }\n\n    LGraphCanvas.prototype.drawSubgraphPanelLeft = function (subgraph, subnode, ctx) {\n        var num = subnode.inputs ? subnode.inputs.length : 0;\n        var w = 200;\n        var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6);\n\n        ctx.fillStyle = \"#111\";\n        ctx.globalAlpha = 0.8;\n        ctx.beginPath();\n        ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]);\n        ctx.fill();\n        ctx.globalAlpha = 1;\n\n        ctx.fillStyle = \"#888\";\n        ctx.font = \"14px Arial\";\n        ctx.textAlign = \"left\";\n        ctx.fillText(\"Graph Inputs\", 20, 34);\n        // var pos = this.mouse;\n\n        if (this.drawButton(w - 20, 20, 20, 20, \"X\", \"#151515\")) {\n            this.closeSubgraph();\n            return;\n        }\n\n        var y = 50;\n        ctx.font = \"14px Arial\";\n        if (subnode.inputs)\n            for (var i = 0; i < subnode.inputs.length; ++i) {\n                var input = subnode.inputs[i];\n                if (input.not_subgraph_input)\n                    continue;\n\n                //input button clicked\n                if (this.drawButton(20, y + 2, w - 20, h - 2)) {\n                    var type = subnode.constructor.input_node_type || \"graph/input\";\n                    this.graph.beforeChange();\n                    var newnode = LiteGraph.createNode(type);\n                    if (newnode) {\n                        subgraph.add(newnode);\n                        this.block_click = false;\n                        this.last_click_position = null;\n                        this.selectNodes([newnode]);\n                        this.node_dragged = newnode;\n                        this.dragging_canvas = false;\n                        newnode.setProperty(\"name\", input.name);\n                        newnode.setProperty(\"type\", input.type);\n                        this.node_dragged.pos[0] = this.graph_mouse[0] - 5;\n                        this.node_dragged.pos[1] = this.graph_mouse[1] - 5;\n                        this.graph.afterChange();\n                    }\n                    else\n                        console.error(\"graph input node not found:\", type);\n                }\n                ctx.fillStyle = \"#9C9\";\n                ctx.beginPath();\n                ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI);\n                ctx.fill();\n                ctx.fillStyle = \"#AAA\";\n                ctx.fillText(input.name, 30, y + h * 0.75);\n                // var tw = ctx.measureText(input.name);\n                ctx.fillStyle = \"#777\";\n                ctx.fillText(input.type, 130, y + h * 0.75);\n                y += h;\n            }\n        //add + button\n        if (this.drawButton(20, y + 2, w - 20, h - 2, \"+\", \"#151515\", \"#222\")) {\n            this.showSubgraphPropertiesDialog(subnode);\n        }\n    }\n    LGraphCanvas.prototype.drawSubgraphPanelRight = function (subgraph, subnode, ctx) {\n        var num = subnode.outputs ? subnode.outputs.length : 0;\n        var canvas_w = this.bgcanvas.width\n        var w = 200;\n        var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6);\n\n        ctx.fillStyle = \"#111\";\n        ctx.globalAlpha = 0.8;\n        ctx.beginPath();\n        ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]);\n        ctx.fill();\n        ctx.globalAlpha = 1;\n\n        ctx.fillStyle = \"#888\";\n        ctx.font = \"14px Arial\";\n        ctx.textAlign = \"left\";\n        var title_text = \"Graph Outputs\"\n        var tw = ctx.measureText(title_text).width\n        ctx.fillText(title_text, (canvas_w - tw) - 20, 34);\n        // var pos = this.mouse;\n        if (this.drawButton(canvas_w - w, 20, 20, 20, \"X\", \"#151515\")) {\n            this.closeSubgraph();\n            return;\n        }\n\n        var y = 50;\n        ctx.font = \"14px Arial\";\n        if (subnode.outputs)\n            for (var i = 0; i < subnode.outputs.length; ++i) {\n                var output = subnode.outputs[i];\n                if (output.not_subgraph_input)\n                    continue;\n\n                //output button clicked\n                if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2)) {\n                    var type = subnode.constructor.output_node_type || \"graph/output\";\n                    this.graph.beforeChange();\n                    var newnode = LiteGraph.createNode(type);\n                    if (newnode) {\n                        subgraph.add(newnode);\n                        this.block_click = false;\n                        this.last_click_position = null;\n                        this.selectNodes([newnode]);\n                        this.node_dragged = newnode;\n                        this.dragging_canvas = false;\n                        newnode.setProperty(\"name\", output.name);\n                        newnode.setProperty(\"type\", output.type);\n                        this.node_dragged.pos[0] = this.graph_mouse[0] - 5;\n                        this.node_dragged.pos[1] = this.graph_mouse[1] - 5;\n                        this.graph.afterChange();\n                    }\n                    else\n                        console.error(\"graph input node not found:\", type);\n                }\n                ctx.fillStyle = \"#9C9\";\n                ctx.beginPath();\n                ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI);\n                ctx.fill();\n                ctx.fillStyle = \"#AAA\";\n                ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75);\n                // var tw = ctx.measureText(input.name);\n                ctx.fillStyle = \"#777\";\n                ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75);\n                y += h;\n            }\n        //add + button\n        if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2, \"+\", \"#151515\", \"#222\")) {\n            this.showSubgraphPropertiesDialogRight(subnode);\n        }\n    }\n\t//Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm\n\tLGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor )\n\t{\n\t\tvar ctx = this.ctx;\n\t\tbgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR;\n\t\thovercolor = hovercolor || \"#555\";\n\t\ttextcolor = textcolor || LiteGraph.NODE_TEXT_COLOR;\n\t\tvar pos = this.ds.convertOffsetToCanvas(this.graph_mouse);\n\t\tvar hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tpos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null;\n        if(pos) {\n            var rect = this.canvas.getBoundingClientRect();\n            pos[0] -= rect.left;\n            pos[1] -= rect.top;\n        }\n\t\tvar clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\n\t\tctx.fillStyle = hover ? hovercolor : bgcolor;\n\t\tif(clicked)\n\t\t\tctx.fillStyle = \"#AAA\";\n\t\tctx.beginPath();\n\t\tctx.roundRect(x,y,w,h,[4] );\n\t\tctx.fill();\n\n\t\tif(text != null)\n\t\t{\n\t\t\tif(text.constructor == String)\n\t\t\t{\n\t\t\t\tctx.fillStyle = textcolor;\n\t\t\t\tctx.textAlign = \"center\";\n\t\t\t\tctx.font = ((h * 0.65)|0) + \"px Arial\";\n\t\t\t\tctx.fillText( text, x + w * 0.5,y + h * 0.75 );\n\t\t\t\tctx.textAlign = \"left\";\n\t\t\t}\n\t\t}\n\n\t\tvar was_clicked = clicked && !this.block_click;\n\t\tif(clicked)\n\t\t\tthis.blockClick();\n\t\treturn was_clicked;\n\t}\n\n\tLGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click )\n\t{\n\t\tvar pos = this.mouse;\n\t\tvar hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tpos = this.last_click_position;\n\t\tvar clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tvar was_clicked = clicked && !this.block_click;\n\t\tif(clicked && hold_click)\n\t\t\tthis.blockClick();\n\t\treturn was_clicked;\n\t}\n\n    /**\n     * draws some useful stats in the corner of the canvas\n     * @method renderInfo\n     **/\n    LGraphCanvas.prototype.renderInfo = function(ctx, x, y) {\n        x = x || 10;\n        y = y || this.canvas.height - 80;\n\n        ctx.save();\n        ctx.translate(x, y);\n\n        ctx.font = \"10px Arial\";\n        ctx.fillStyle = \"#888\";\n\t\tctx.textAlign = \"left\";\n        if (this.graph) {\n            ctx.fillText( \"T: \" + this.graph.globaltime.toFixed(2) + \"s\", 5, 13 * 1 );\n            ctx.fillText(\"I: \" + this.graph.iteration, 5, 13 * 2 );\n            ctx.fillText(\"N: \" + this.graph._nodes.length + \" [\" + this.visible_nodes.length + \"]\", 5, 13 * 3 );\n            ctx.fillText(\"V: \" + this.graph._version, 5, 13 * 4);\n            ctx.fillText(\"FPS:\" + this.fps.toFixed(2), 5, 13 * 5);\n        } else {\n            ctx.fillText(\"No graph selected\", 5, 13 * 1);\n        }\n        ctx.restore();\n    };\n\n    /**\n     * draws the back canvas (the one containing the background and the connections)\n     * @method drawBackCanvas\n     **/\n    LGraphCanvas.prototype.drawBackCanvas = function() {\n        var canvas = this.bgcanvas;\n        if (\n            canvas.width != this.canvas.width ||\n            canvas.height != this.canvas.height\n        ) {\n            canvas.width = this.canvas.width;\n            canvas.height = this.canvas.height;\n        }\n\n        if (!this.bgctx) {\n            this.bgctx = this.bgcanvas.getContext(\"2d\");\n        }\n        var ctx = this.bgctx;\n        if (ctx.start) {\n            ctx.start();\n        }\n\n\t\tvar viewport = this.viewport || [0,0,ctx.canvas.width,ctx.canvas.height];\n\n        //clear\n        if (this.clear_background) {\n            ctx.clearRect( viewport[0], viewport[1], viewport[2], viewport[3] );\n        }\n\n\t\t//show subgraph stack header\n        if (this._graph_stack && this._graph_stack.length) {\n            ctx.save();\n            var parent_graph = this._graph_stack[this._graph_stack.length - 1];\n            var subgraph_node = this.graph._subgraph_node;\n            ctx.strokeStyle = subgraph_node.bgcolor;\n            ctx.lineWidth = 10;\n            ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2);\n            ctx.lineWidth = 1;\n            ctx.font = \"40px Arial\";\n            ctx.textAlign = \"center\";\n            ctx.fillStyle = subgraph_node.bgcolor || \"#AAA\";\n            var title = \"\";\n            for (var i = 1; i < this._graph_stack.length; ++i) {\n                title +=\n                    this._graph_stack[i]._subgraph_node.getTitle() + \" >> \";\n            }\n            ctx.fillText(\n                title + subgraph_node.getTitle(),\n                canvas.width * 0.5,\n                40\n            );\n            ctx.restore();\n        }\n\n        var bg_already_painted = false;\n        if (this.onRenderBackground) {\n            bg_already_painted = this.onRenderBackground(canvas, ctx);\n        }\n\n        //reset in case of error\n        if ( !this.viewport )\n\t\t{\n\t        ctx.restore();\n\t\t    ctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t}\n        this.visible_links.length = 0;\n\n        if (this.graph) {\n            //apply transformations\n            ctx.save();\n            this.ds.toCanvasContext(ctx);\n\n            //render BG\n            if ( this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color )\n            {\n                ctx.fillStyle = this.clear_background_color;\n                ctx.fillRect(\n                    this.visible_area[0],\n                    this.visible_area[1],\n                    this.visible_area[2],\n                    this.visible_area[3]\n                );\n            }\n\n            if (\n                this.background_image &&\n                this.ds.scale > 0.5 &&\n                !bg_already_painted\n            ) {\n                if (this.zoom_modify_alpha) {\n                    ctx.globalAlpha =\n                        (1.0 - 0.5 / this.ds.scale) * this.editor_alpha;\n                } else {\n                    ctx.globalAlpha = this.editor_alpha;\n                }\n                ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = \n                if (\n                    !this._bg_img ||\n                    this._bg_img.name != this.background_image\n                ) {\n                    this._bg_img = new Image();\n                    this._bg_img.name = this.background_image;\n                    this._bg_img.src = this.background_image;\n                    var that = this;\n                    this._bg_img.onload = function() {\n                        that.draw(true, true);\n                    };\n                }\n\n                var pattern = null;\n                if (this._pattern == null && this._bg_img.width > 0) {\n                    pattern = ctx.createPattern(this._bg_img, \"repeat\");\n                    this._pattern_img = this._bg_img;\n                    this._pattern = pattern;\n                } else {\n                    pattern = this._pattern;\n                }\n                if (pattern) {\n                    ctx.fillStyle = pattern;\n                    ctx.fillRect(\n                        this.visible_area[0],\n                        this.visible_area[1],\n                        this.visible_area[2],\n                        this.visible_area[3]\n                    );\n                    ctx.fillStyle = \"transparent\";\n                }\n\n                ctx.globalAlpha = 1.0;\n                ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled\n            }\n\n            //groups\n            if (this.graph._groups.length && !this.live_mode) {\n                this.drawGroups(canvas, ctx);\n            }\n\n            if (this.onDrawBackground) {\n                this.onDrawBackground(ctx, this.visible_area);\n            }\n            if (this.onBackgroundRender) {\n                //LEGACY\n                console.error(\n                    \"WARNING! onBackgroundRender deprecated, now is named onDrawBackground \"\n                );\n                this.onBackgroundRender = null;\n            }\n\n            //DEBUG: show clipping area\n            //ctx.fillStyle = \"red\";\n            //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20);\n\n            //bg\n            if (this.render_canvas_border) {\n                ctx.strokeStyle = \"#235\";\n                ctx.strokeRect(0, 0, canvas.width, canvas.height);\n            }\n\n            if (this.render_connections_shadows) {\n                ctx.shadowColor = \"#000\";\n                ctx.shadowOffsetX = 0;\n                ctx.shadowOffsetY = 0;\n                ctx.shadowBlur = 6;\n            } else {\n                ctx.shadowColor = \"rgba(0,0,0,0)\";\n            }\n\n            //draw connections\n            if (!this.live_mode) {\n                this.drawConnections(ctx);\n            }\n\n            ctx.shadowColor = \"rgba(0,0,0,0)\";\n\n            //restore state\n            ctx.restore();\n        }\n\n        if (ctx.finish) {\n            ctx.finish();\n        }\n\n        this.dirty_bgcanvas = false;\n        this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas\n    };\n\n    var temp_vec2 = new Float32Array(2);\n\n    /**\n     * draws the given node inside the canvas\n     * @method drawNode\n     **/\n    LGraphCanvas.prototype.drawNode = function(node, ctx) {\n        var glow = false;\n        this.current_node = node;\n\n        var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR;\n        var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;\n\n        //shadow and glow\n        if (node.mouseOver) {\n            glow = true;\n        }\n\n        var low_quality = this.ds.scale < 0.6; //zoomed out\n\n        //only render if it forces it to do it\n        if (this.live_mode) {\n            if (!node.flags.collapsed) {\n                ctx.shadowColor = \"transparent\";\n                if (node.onDrawForeground) {\n                    node.onDrawForeground(ctx, this, this.canvas);\n                }\n            }\n            return;\n        }\n\n        var editor_alpha = this.editor_alpha;\n        ctx.globalAlpha = editor_alpha;\n\n        if (this.render_shadows && !low_quality) {\n            ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;\n            ctx.shadowOffsetX = 2 * this.ds.scale;\n            ctx.shadowOffsetY = 2 * this.ds.scale;\n            ctx.shadowBlur = 3 * this.ds.scale;\n        } else {\n            ctx.shadowColor = \"transparent\";\n        }\n\n        //custom draw collapsed method (draw after shadows because they are affected)\n        if (\n            node.flags.collapsed &&\n            node.onDrawCollapsed &&\n            node.onDrawCollapsed(ctx, this) == true\n        ) {\n            return;\n        }\n\n        //clip if required (mask)\n        var shape = node._shape || LiteGraph.BOX_SHAPE;\n        var size = temp_vec2;\n        temp_vec2.set(node.size);\n        var horizontal = node.horizontal; // || node.flags.horizontal;\n\n        if (node.flags.collapsed) {\n            ctx.font = this.inner_text_font;\n            var title = node.getTitle ? node.getTitle() : node.title;\n            if (title != null) {\n                node._collapsed_width = Math.min(\n                    node.size[0],\n                    ctx.measureText(title).width +\n                        LiteGraph.NODE_TITLE_HEIGHT * 2\n                ); //LiteGraph.NODE_COLLAPSED_WIDTH;\n                size[0] = node._collapsed_width;\n                size[1] = 0;\n            }\n        }\n\n        if (node.clip_area) {\n            //Start clipping\n            ctx.save();\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE) {\n                ctx.rect(0, 0, size[0], size[1]);\n            } else if (shape == LiteGraph.ROUND_SHAPE) {\n                ctx.roundRect(0, 0, size[0], size[1], [10]);\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.clip();\n        }\n\n        //draw shape\n        if (node.has_errors) {\n            bgcolor = \"red\";\n        }\n        this.drawNodeShape(\n            node,\n            ctx,\n            size,\n            color,\n            bgcolor,\n            node.is_selected,\n            node.mouseOver\n        );\n        ctx.shadowColor = \"transparent\";\n\n        //draw foreground\n        if (node.onDrawForeground) {\n            node.onDrawForeground(ctx, this, this.canvas);\n        }\n\n        //connection slots\n        ctx.textAlign = horizontal ? \"center\" : \"left\";\n        ctx.font = this.inner_text_font;\n\n        var render_text = !low_quality;\n\n        var out_slot = this.connecting_output;\n        var in_slot = this.connecting_input;\n        ctx.lineWidth = 1;\n\n        var max_y = 0;\n        var slot_pos = new Float32Array(2); //to reuse\n\n        //render inputs and outputs\n        if (!node.flags.collapsed) {\n            //input connection slots\n            if (node.inputs) {\n                for (var i = 0; i < node.inputs.length; i++) {\n                    var slot = node.inputs[i];\n                    \n                    var slot_type = slot.type;\n                    var slot_shape = slot.shape;\n                    \n                    ctx.globalAlpha = editor_alpha;\n                    //change opacity of incompatible slots when dragging a connection\n                    if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) {\n                        ctx.globalAlpha = 0.4 * editor_alpha;\n                    }\n\n                    ctx.fillStyle =\n                        slot.link != null\n                            ? slot.color_on ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.input_on\n                            : slot.color_off ||\n                              this.default_connection_color_byTypeOff[slot_type] ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.input_off;\n\n                    var pos = node.getConnectionPos(true, i, slot_pos);\n                    pos[0] -= node.pos[0];\n                    pos[1] -= node.pos[1];\n                    if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) {\n                        max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n                    }\n\n                    ctx.beginPath();\n\n\t\t\t\t\tif (slot_type == \"array\"){\n                        slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead?\n                    }\n                    \n                    var doStroke = true;\n                    \n                    if (\n                        slot.type === LiteGraph.EVENT ||\n                        slot.shape === LiteGraph.BOX_SHAPE\n                    ) {\n                        if (horizontal) {\n                            ctx.rect(\n                                pos[0] - 5 + 0.5,\n                                pos[1] - 8 + 0.5,\n                                10,\n                                14\n                            );\n                        } else {\n                            ctx.rect(\n                                pos[0] - 6 + 0.5,\n                                pos[1] - 5 + 0.5,\n                                14,\n                                10\n                            );\n                        }\n                    } else if (slot_shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(pos[0] + 8, pos[1] + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else if (slot_shape === LiteGraph.GRID_SHAPE) {\n                        ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2);\n                        doStroke = false;\n                    } else {\n\t\t\t\t\t\tif(low_quality)\n\t                        ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster\n\t\t\t\t\t\telse\n\t                        ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);\n                    }\n                    ctx.fill();\n\n                    //render name\n                    if (render_text) {\n                        var text = slot.label != null ? slot.label : slot.name;\n                        if (text) {\n                            ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;\n                            if (horizontal || slot.dir == LiteGraph.UP) {\n                                ctx.fillText(text, pos[0], pos[1] - 10);\n                            } else {\n                                ctx.fillText(text, pos[0] + 10, pos[1] + 5);\n                            }\n                        }\n                    }\n                }\n            }\n\n            //output connection slots\n\n            ctx.textAlign = horizontal ? \"center\" : \"right\";\n            ctx.strokeStyle = \"black\";\n            if (node.outputs) {\n                for (var i = 0; i < node.outputs.length; i++) {\n                    var slot = node.outputs[i];\n                    \n                    var slot_type = slot.type;\n                    var slot_shape = slot.shape;\n                    \n                    //change opacity of incompatible slots when dragging a connection\n                    if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) {\n                        ctx.globalAlpha = 0.4 * editor_alpha;\n                    }\n                    \n                    var pos = node.getConnectionPos(false, i, slot_pos);\n                    pos[0] -= node.pos[0];\n                    pos[1] -= node.pos[1];\n                    if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) {\n                        max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n                    }\n\n                    ctx.fillStyle =\n                        slot.links && slot.links.length\n                            ? slot.color_on ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.output_on\n                            : slot.color_off ||\n                              this.default_connection_color_byTypeOff[slot_type] ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.output_off;\n                    ctx.beginPath();\n                    //ctx.rect( node.size[0] - 14,i*14,10,10);\n\n\t\t\t\t\tif (slot_type == \"array\"){\n                        slot_shape = LiteGraph.GRID_SHAPE;\n                    }\n                    \n                    var doStroke = true;\n                    \n                    if (\n                        slot_type === LiteGraph.EVENT ||\n                        slot_shape === LiteGraph.BOX_SHAPE\n                    ) {\n                        if (horizontal) {\n                            ctx.rect(\n                                pos[0] - 5 + 0.5,\n                                pos[1] - 8 + 0.5,\n                                10,\n                                14\n                            );\n                        } else {\n                            ctx.rect(\n                                pos[0] - 6 + 0.5,\n                                pos[1] - 5 + 0.5,\n                                14,\n                                10\n                            );\n                        }\n                    } else if (slot_shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(pos[0] + 8, pos[1] + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);\n                        ctx.closePath();\n                    }  else if (slot_shape === LiteGraph.GRID_SHAPE) {\n                        ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2);\n                        doStroke = false;\n                    } else {\n\t\t\t\t\t\tif(low_quality)\n\t                        ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 );\n\t\t\t\t\t\telse\n\t                        ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);\n                    }\n\n                    //trigger\n                    //if(slot.node_id != null && slot.slot == -1)\n                    //\tctx.fillStyle = \"#F85\";\n\n                    //if(slot.links != null && slot.links.length)\n                    ctx.fill();\n\t\t\t\t\tif(!low_quality && doStroke)\n\t                    ctx.stroke();\n\n                    //render output name\n                    if (render_text) {\n                        var text = slot.label != null ? slot.label : slot.name;\n                        if (text) {\n                            ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;\n                            if (horizontal || slot.dir == LiteGraph.DOWN) {\n                                ctx.fillText(text, pos[0], pos[1] - 8);\n                            } else {\n                                ctx.fillText(text, pos[0] - 10, pos[1] + 5);\n                            }\n                        }\n                    }\n                }\n            }\n\n            ctx.textAlign = \"left\";\n            ctx.globalAlpha = 1;\n\n            if (node.widgets) {\n\t\t\t\tvar widgets_y = max_y;\n                if (horizontal || node.widgets_up) {\n                    widgets_y = 2;\n                }\n\t\t\t\tif( node.widgets_start_y != null )\n                    widgets_y = node.widgets_start_y;\n                this.drawNodeWidgets(\n                    node,\n                    widgets_y,\n                    ctx,\n                    this.node_widget && this.node_widget[0] == node\n                        ? this.node_widget[1]\n                        : null\n                );\n            }\n        } else if (this.render_collapsed_slots) {\n            //if collapsed\n            var input_slot = null;\n            var output_slot = null;\n\n            //get first connected slot to render\n            if (node.inputs) {\n                for (var i = 0; i < node.inputs.length; i++) {\n                    var slot = node.inputs[i];\n                    if (slot.link == null) {\n                        continue;\n                    }\n                    input_slot = slot;\n                    break;\n                }\n            }\n            if (node.outputs) {\n                for (var i = 0; i < node.outputs.length; i++) {\n                    var slot = node.outputs[i];\n                    if (!slot.links || !slot.links.length) {\n                        continue;\n                    }\n                    output_slot = slot;\n                }\n            }\n\n            if (input_slot) {\n                var x = 0;\n                var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center\n                if (horizontal) {\n                    x = node._collapsed_width * 0.5;\n                    y = -LiteGraph.NODE_TITLE_HEIGHT;\n                }\n                ctx.fillStyle = \"#686\";\n                ctx.beginPath();\n                if (\n                    slot.type === LiteGraph.EVENT ||\n                    slot.shape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(x - 7 + 0.5, y - 4, 14, 8);\n                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(x + 8, y);\n                    ctx.lineTo(x + -4, y - 4);\n                    ctx.lineTo(x + -4, y + 4);\n                    ctx.closePath();\n                } else {\n                    ctx.arc(x, y, 4, 0, Math.PI * 2);\n                }\n                ctx.fill();\n            }\n\n            if (output_slot) {\n                var x = node._collapsed_width;\n                var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center\n                if (horizontal) {\n                    x = node._collapsed_width * 0.5;\n                    y = 0;\n                }\n                ctx.fillStyle = \"#686\";\n                ctx.strokeStyle = \"black\";\n                ctx.beginPath();\n                if (\n                    slot.type === LiteGraph.EVENT ||\n                    slot.shape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(x - 7 + 0.5, y - 4, 14, 8);\n                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(x + 6, y);\n                    ctx.lineTo(x - 6, y - 4);\n                    ctx.lineTo(x - 6, y + 4);\n                    ctx.closePath();\n                } else {\n                    ctx.arc(x, y, 4, 0, Math.PI * 2);\n                }\n                ctx.fill();\n                //ctx.stroke();\n            }\n        }\n\n        if (node.clip_area) {\n            ctx.restore();\n        }\n\n        ctx.globalAlpha = 1.0;\n    };\n\n\t//used by this.over_link_center\n\tLGraphCanvas.prototype.drawLinkTooltip = function( ctx, link )\n\t{\n\t\tvar pos = link._pos;\n\t\tctx.fillStyle = \"black\";\n\t\tctx.beginPath();\n\t\tctx.arc( pos[0], pos[1], 3, 0, Math.PI * 2 );\n\t\tctx.fill();\n\n\t\tif(link.data == null)\n\t\t\treturn;\n\n\t\tif(this.onDrawLinkTooltip)\n\t\t\tif( this.onDrawLinkTooltip(ctx,link,this) == true )\n\t\t\t\treturn;\n\n\t\tvar data = link.data;\n\t\tvar text = null;\n\n\t\tif( data.constructor === Number )\n\t\t\ttext = data.toFixed(2);\n\t\telse if( data.constructor === String )\n\t\t\ttext = \"\\\"\" + data + \"\\\"\";\n\t\telse if( data.constructor === Boolean )\n\t\t\ttext = String(data);\n\t\telse if (data.toToolTip)\n\t\t\ttext = data.toToolTip();\n\t\telse\n\t\t\ttext = \"[\" + data.constructor.name + \"]\";\n\n\t\tif(text == null)\n\t\t\treturn;\n\t\ttext = text.substr(0,30); //avoid weird\n\n\t\tctx.font = \"14px Courier New\";\n\t\tvar info = ctx.measureText(text);\n\t\tvar w = info.width + 20;\n\t\tvar h = 24;\n\t\tctx.shadowColor = \"black\";\n\t\tctx.shadowOffsetX = 2;\n\t\tctx.shadowOffsetY = 2;\n\t\tctx.shadowBlur = 3;\n\t\tctx.fillStyle = \"#454\";\n\t\tctx.beginPath();\n\t\tctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]);\n\t\tctx.moveTo( pos[0] - 10, pos[1] - 15 );\n\t\tctx.lineTo( pos[0] + 10, pos[1] - 15 );\n\t\tctx.lineTo( pos[0], pos[1] - 5 );\n\t\tctx.fill();\n        ctx.shadowColor = \"transparent\";\n\t\tctx.textAlign = \"center\";\n\t\tctx.fillStyle = \"#CEC\";\n\t\tctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3);\n\t}\n\n    /**\n     * draws the shape of the given node in the canvas\n     * @method drawNodeShape\n     **/\n    var tmp_area = new Float32Array(4);\n\n    LGraphCanvas.prototype.drawNodeShape = function(\n        node,\n        ctx,\n        size,\n        fgcolor,\n        bgcolor,\n        selected,\n        mouse_over\n    ) {\n        //bg rect\n        ctx.strokeStyle = fgcolor;\n        ctx.fillStyle = bgcolor;\n\n        var title_height = LiteGraph.NODE_TITLE_HEIGHT;\n        var low_quality = this.ds.scale < 0.5;\n\n        //render node area depending on shape\n        var shape =\n            node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;\n\n        var title_mode = node.constructor.title_mode;\n\n        var render_title = true;\n        if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) {\n            render_title = false;\n        } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) {\n            render_title = true;\n        }\n\n        var area = tmp_area;\n        area[0] = 0; //x\n        area[1] = render_title ? -title_height : 0; //y\n        area[2] = size[0] + 1; //w\n        area[3] = render_title ? size[1] + title_height : size[1]; //h\n\n        var old_alpha = ctx.globalAlpha;\n\n        //full node shape\n        //if(node.flags.collapsed)\n        {\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE || low_quality) {\n                ctx.fillRect(area[0], area[1], area[2], area[3]);\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                shape == LiteGraph.CARD_SHAPE\n            ) {\n                ctx.roundRect(\n                    area[0],\n                    area[1],\n                    area[2],\n                    area[3],\n                    shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] \n                );\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.fill();\n\n\t\t\t//separator\n\t\t\tif(!node.flags.collapsed && render_title)\n\t\t\t{\n\t\t\t\tctx.shadowColor = \"transparent\";\n\t\t\t\tctx.fillStyle = \"rgba(0,0,0,0.2)\";\n\t\t\t\tctx.fillRect(0, -1, area[2], 2);\n\t\t\t}\n        }\n        ctx.shadowColor = \"transparent\";\n\n        if (node.onDrawBackground) {\n            node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse );\n        }\n\n        //title bg (remember, it is rendered ABOVE the node)\n        if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) {\n            //title bar\n            if (node.onDrawTitleBar) {\n                node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor );\n            } else if (\n                title_mode != LiteGraph.TRANSPARENT_TITLE &&\n                (node.constructor.title_color || this.render_title_colored)\n            ) {\n                var title_color = node.constructor.title_color || fgcolor;\n\n                if (node.flags.collapsed) {\n                    ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;\n                }\n\n                //* gradient test\n                if (this.use_gradients) {\n                    var grad = LGraphCanvas.gradients[title_color];\n                    if (!grad) {\n                        grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0);\n                        grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException\n                        grad.addColorStop(1, \"#000\");\n                    }\n                    ctx.fillStyle = grad;\n                } else {\n                    ctx.fillStyle = title_color;\n                }\n\n                //ctx.globalAlpha = 0.5 * old_alpha;\n                ctx.beginPath();\n                if (shape == LiteGraph.BOX_SHAPE || low_quality) {\n                    ctx.rect(0, -title_height, size[0] + 1, title_height);\n                } else if (  shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) {\n                    ctx.roundRect(\n                        0,\n                        -title_height,\n                        size[0] + 1,\n                        title_height,\n                        node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0]\n                    );\n                }\n                ctx.fill();\n                ctx.shadowColor = \"transparent\";\n            }\n\n            var colState = false;\n            if (LiteGraph.node_box_coloured_by_mode){\n                if(LiteGraph.NODE_MODES_COLORS[node.mode]){\n                    colState = LiteGraph.NODE_MODES_COLORS[node.mode];\n                }\n            }\n            if (LiteGraph.node_box_coloured_when_on){\n                colState = node.action_triggered ? \"#FFF\" : (node.execute_triggered ? \"#AAA\" : colState);\n            }\n            \n            //title box\n            var box_size = 10;\n            if (node.onDrawTitleBox) {\n                node.onDrawTitleBox(ctx, title_height, size, this.ds.scale);\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                shape == LiteGraph.CIRCLE_SHAPE ||\n                shape == LiteGraph.CARD_SHAPE\n            ) {\n                if (low_quality) {\n                    ctx.fillStyle = \"black\";\n                    ctx.beginPath();\n                    ctx.arc(\n                        title_height * 0.5,\n                        title_height * -0.5,\n                        box_size * 0.5 + 1,\n                        0,\n                        Math.PI * 2\n                    );\n                    ctx.fill();\n                }\n                \n                ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR;\n\t\t\t\tif(low_quality)\n\t\t\t\t\tctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size  );\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.arc(\n\t\t\t\t\t\ttitle_height * 0.5,\n\t\t\t\t\t\ttitle_height * -0.5,\n\t\t\t\t\t\tbox_size * 0.5,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tMath.PI * 2\n\t\t\t\t\t);\n\t\t\t\t\tctx.fill();\n\t\t\t\t}\n            } else {\n                if (low_quality) {\n                    ctx.fillStyle = \"black\";\n                    ctx.fillRect(\n                        (title_height - box_size) * 0.5 - 1,\n                        (title_height + box_size) * -0.5 - 1,\n                        box_size + 2,\n                        box_size + 2\n                    );\n                }\n                ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR;\n                ctx.fillRect(\n                    (title_height - box_size) * 0.5,\n                    (title_height + box_size) * -0.5,\n                    box_size,\n                    box_size\n                );\n            }\n            ctx.globalAlpha = old_alpha;\n\n            //title text\n            if (node.onDrawTitleText) {\n                node.onDrawTitleText(\n                    ctx,\n                    title_height,\n                    size,\n                    this.ds.scale,\n                    this.title_text_font,\n                    selected\n                );\n            }\n            if (!low_quality) {\n                ctx.font = this.title_text_font;\n                var title = String(node.getTitle());\n                if (title) {\n                    if (selected) {\n                        ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR;\n                    } else {\n                        ctx.fillStyle =\n                            node.constructor.title_text_color ||\n                            this.node_title_color;\n                    }\n                    if (node.flags.collapsed) {\n                        ctx.textAlign = \"left\";\n                        var measure = ctx.measureText(title);\n                        ctx.fillText(\n                            title.substr(0,20), //avoid urls too long\n                            title_height,// + measure.width * 0.5,\n                            LiteGraph.NODE_TITLE_TEXT_Y - title_height\n                        );\n                        ctx.textAlign = \"left\";\n                    } else {\n                        ctx.textAlign = \"left\";\n                        ctx.fillText(\n                            title,\n                            title_height,\n                            LiteGraph.NODE_TITLE_TEXT_Y - title_height\n                        );\n                    }\n                }\n            }\n\n\t\t\t//subgraph box\n\t\t\tif (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) {\n\t\t\t\tvar w = LiteGraph.NODE_TITLE_HEIGHT;\n\t\t\t\tvar x = node.size[0] - w;\n\t\t\t\tvar over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 );\n\t\t\t\tctx.fillStyle = over ? \"#888\" : \"#555\";\n\t\t\t\tif( shape == LiteGraph.BOX_SHAPE || low_quality)\n\t\t\t\t\tctx.fillRect(x+2, -w+2, w-4, w-4);\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.roundRect(x+2, -w+2, w-4, w-4,[4]);\n\t\t\t\t\tctx.fill();\n\t\t\t\t}\n\t\t\t\tctx.fillStyle = \"#333\";\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.moveTo(x + w * 0.2, -w * 0.6);\n\t\t\t\tctx.lineTo(x + w * 0.8, -w * 0.6);\n\t\t\t\tctx.lineTo(x + w * 0.5, -w * 0.3);\n\t\t\t\tctx.fill();\n\t\t\t}\n\n\t\t\t//custom title render\n            if (node.onDrawTitle) {\n                node.onDrawTitle(ctx);\n            }\n        }\n\n        //render selection marker\n        if (selected) {\n            if (node.onBounding) {\n                node.onBounding(area);\n            }\n\n            if (title_mode == LiteGraph.TRANSPARENT_TITLE) {\n                area[1] -= title_height;\n                area[3] += title_height;\n            }\n            ctx.lineWidth = 1;\n            ctx.globalAlpha = 0.8;\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE) {\n                ctx.rect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3]\n                );\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed)\n            ) {\n                ctx.roundRect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3],\n                    [this.round_radius * 2]\n                );\n            } else if (shape == LiteGraph.CARD_SHAPE) {\n                ctx.roundRect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3],\n                    [this.round_radius * 2,2,this.round_radius * 2,2]\n                );\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5 + 6,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR;\n            ctx.stroke();\n            ctx.strokeStyle = fgcolor;\n            ctx.globalAlpha = 1;\n        }\n        \n        // these counter helps in conditioning drawing based on if the node has been executed or an action occurred\n        if (node.execute_triggered>0) node.execute_triggered--;\n        if (node.action_triggered>0) node.action_triggered--;\n    };\n\n    var margin_area = new Float32Array(4);\n    var link_bounding = new Float32Array(4);\n    var tempA = new Float32Array(2);\n    var tempB = new Float32Array(2);\n\n    /**\n     * draws every connection visible in the canvas\n     * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time\n     * @method drawConnections\n     **/\n    LGraphCanvas.prototype.drawConnections = function(ctx) {\n        var now = LiteGraph.getTime();\n        var visible_area = this.visible_area;\n        margin_area[0] = visible_area[0] - 20;\n        margin_area[1] = visible_area[1] - 20;\n        margin_area[2] = visible_area[2] + 40;\n        margin_area[3] = visible_area[3] + 40;\n\n        //draw connections\n        ctx.lineWidth = this.connections_width;\n\n        ctx.fillStyle = \"#AAA\";\n        ctx.strokeStyle = \"#AAA\";\n        ctx.globalAlpha = this.editor_alpha;\n        //for every node\n        var nodes = this.graph._nodes;\n        for (var n = 0, l = nodes.length; n < l; ++n) {\n            var node = nodes[n];\n            //for every input (we render just inputs because it is easier as every slot can only have one input)\n            if (!node.inputs || !node.inputs.length) {\n                continue;\n            }\n\n            for (var i = 0; i < node.inputs.length; ++i) {\n                var input = node.inputs[i];\n                if (!input || input.link == null) {\n                    continue;\n                }\n                var link_id = input.link;\n                var link = this.graph.links[link_id];\n                if (!link) {\n                    continue;\n                }\n\n                //find link info\n                var start_node = this.graph.getNodeById(link.origin_id);\n                if (start_node == null) {\n                    continue;\n                }\n                var start_node_slot = link.origin_slot;\n                var start_node_slotpos = null;\n                if (start_node_slot == -1) {\n                    start_node_slotpos = [\n                        start_node.pos[0] + 10,\n                        start_node.pos[1] + 10\n                    ];\n                } else {\n                    start_node_slotpos = start_node.getConnectionPos(\n                        false,\n                        start_node_slot,\n                        tempA\n                    );\n                }\n                var end_node_slotpos = node.getConnectionPos(true, i, tempB);\n\n                //compute link bounding\n                link_bounding[0] = start_node_slotpos[0];\n                link_bounding[1] = start_node_slotpos[1];\n                link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0];\n                link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1];\n                if (link_bounding[2] < 0) {\n                    link_bounding[0] += link_bounding[2];\n                    link_bounding[2] = Math.abs(link_bounding[2]);\n                }\n                if (link_bounding[3] < 0) {\n                    link_bounding[1] += link_bounding[3];\n                    link_bounding[3] = Math.abs(link_bounding[3]);\n                }\n\n                //skip links outside of the visible area of the canvas\n                if (!overlapBounding(link_bounding, margin_area)) {\n                    continue;\n                }\n\n                var start_slot = start_node.outputs[start_node_slot];\n                var end_slot = node.inputs[i];\n                if (!start_slot || !end_slot) {\n                    continue;\n                }\n                var start_dir =\n                    start_slot.dir ||\n                    (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT);\n                var end_dir =\n                    end_slot.dir ||\n                    (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT);\n\n                this.renderLink(\n                    ctx,\n                    start_node_slotpos,\n                    end_node_slotpos,\n                    link,\n                    false,\n                    0,\n                    null,\n                    start_dir,\n                    end_dir\n                );\n\n                //event triggered rendered on top\n                if (link && link._last_time && now - link._last_time < 1000) {\n                    var f = 2.0 - (now - link._last_time) * 0.002;\n                    var tmp = ctx.globalAlpha;\n                    ctx.globalAlpha = tmp * f;\n                    this.renderLink(\n                        ctx,\n                        start_node_slotpos,\n                        end_node_slotpos,\n                        link,\n                        true,\n                        f,\n                        \"white\",\n                        start_dir,\n                        end_dir\n                    );\n                    ctx.globalAlpha = tmp;\n                }\n            }\n        }\n        ctx.globalAlpha = 1;\n    };\n\n    /**\n     * draws a link between two points\n     * @method renderLink\n     * @param {vec2} a start pos\n     * @param {vec2} b end pos\n     * @param {Object} link the link object with all the link info\n     * @param {boolean} skip_border ignore the shadow of the link\n     * @param {boolean} flow show flow animation (for events)\n     * @param {string} color the color for the link\n     * @param {number} start_dir the direction enum\n     * @param {number} end_dir the direction enum\n     * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb)\n     **/\n    LGraphCanvas.prototype.renderLink = function(\n        ctx,\n        a,\n        b,\n        link,\n        skip_border,\n        flow,\n        color,\n        start_dir,\n        end_dir,\n        num_sublines\n    ) {\n        if (link) {\n            this.visible_links.push(link);\n        }\n\n        //choose color\n        if (!color && link) {\n            color = link.color || LGraphCanvas.link_type_colors[link.type];\n        }\n        if (!color) {\n            color = this.default_link_color;\n        }\n        if (link != null && this.highlighted_links[link.id]) {\n            color = \"#FFF\";\n        }\n\n        start_dir = start_dir || LiteGraph.RIGHT;\n        end_dir = end_dir || LiteGraph.LEFT;\n\n        var dist = distance(a, b);\n\n        if (this.render_connections_border && this.ds.scale > 0.6) {\n            ctx.lineWidth = this.connections_width + 4;\n        }\n        ctx.lineJoin = \"round\";\n        num_sublines = num_sublines || 1;\n        if (num_sublines > 1) {\n            ctx.lineWidth = 0.5;\n        }\n\n        //begin line shape\n        ctx.beginPath();\n        for (var i = 0; i < num_sublines; i += 1) {\n            var offsety = (i - (num_sublines - 1) * 0.5) * 5;\n\n            if (this.links_render_mode == LiteGraph.SPLINE_LINK) {\n                ctx.moveTo(a[0], a[1] + offsety);\n                var start_offset_x = 0;\n                var start_offset_y = 0;\n                var end_offset_x = 0;\n                var end_offset_y = 0;\n                switch (start_dir) {\n                    case LiteGraph.LEFT:\n                        start_offset_x = dist * -0.25;\n                        break;\n                    case LiteGraph.RIGHT:\n                        start_offset_x = dist * 0.25;\n                        break;\n                    case LiteGraph.UP:\n                        start_offset_y = dist * -0.25;\n                        break;\n                    case LiteGraph.DOWN:\n                        start_offset_y = dist * 0.25;\n                        break;\n                }\n                switch (end_dir) {\n                    case LiteGraph.LEFT:\n                        end_offset_x = dist * -0.25;\n                        break;\n                    case LiteGraph.RIGHT:\n                        end_offset_x = dist * 0.25;\n                        break;\n                    case LiteGraph.UP:\n                        end_offset_y = dist * -0.25;\n                        break;\n                    case LiteGraph.DOWN:\n                        end_offset_y = dist * 0.25;\n                        break;\n                }\n                ctx.bezierCurveTo(\n                    a[0] + start_offset_x,\n                    a[1] + start_offset_y + offsety,\n                    b[0] + end_offset_x,\n                    b[1] + end_offset_y + offsety,\n                    b[0],\n                    b[1] + offsety\n                );\n            } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) {\n                ctx.moveTo(a[0], a[1] + offsety);\n                var start_offset_x = 0;\n                var start_offset_y = 0;\n                var end_offset_x = 0;\n                var end_offset_y = 0;\n                switch (start_dir) {\n                    case LiteGraph.LEFT:\n                        start_offset_x = -1;\n                        break;\n                    case LiteGraph.RIGHT:\n                        start_offset_x = 1;\n                        break;\n                    case LiteGraph.UP:\n                        start_offset_y = -1;\n                        break;\n                    case LiteGraph.DOWN:\n                        start_offset_y = 1;\n                        break;\n                }\n                switch (end_dir) {\n                    case LiteGraph.LEFT:\n                        end_offset_x = -1;\n                        break;\n                    case LiteGraph.RIGHT:\n                        end_offset_x = 1;\n                        break;\n                    case LiteGraph.UP:\n                        end_offset_y = -1;\n                        break;\n                    case LiteGraph.DOWN:\n                        end_offset_y = 1;\n                        break;\n                }\n                var l = 15;\n                ctx.lineTo(\n                    a[0] + start_offset_x * l,\n                    a[1] + start_offset_y * l + offsety\n                );\n                ctx.lineTo(\n                    b[0] + end_offset_x * l,\n                    b[1] + end_offset_y * l + offsety\n                );\n                ctx.lineTo(b[0], b[1] + offsety);\n            } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) {\n                ctx.moveTo(a[0], a[1]);\n                var start_x = a[0];\n                var start_y = a[1];\n                var end_x = b[0];\n                var end_y = b[1];\n                if (start_dir == LiteGraph.RIGHT) {\n                    start_x += 10;\n                } else {\n                    start_y += 10;\n                }\n                if (end_dir == LiteGraph.LEFT) {\n                    end_x -= 10;\n                } else {\n                    end_y -= 10;\n                }\n                ctx.lineTo(start_x, start_y);\n                ctx.lineTo((start_x + end_x) * 0.5, start_y);\n                ctx.lineTo((start_x + end_x) * 0.5, end_y);\n                ctx.lineTo(end_x, end_y);\n                ctx.lineTo(b[0], b[1]);\n            } else {\n                return;\n            } //unknown\n        }\n\n        //rendering the outline of the connection can be a little bit slow\n        if (\n            this.render_connections_border &&\n            this.ds.scale > 0.6 &&\n            !skip_border\n        ) {\n            ctx.strokeStyle = \"rgba(0,0,0,0.5)\";\n            ctx.stroke();\n        }\n\n        ctx.lineWidth = this.connections_width;\n        ctx.fillStyle = ctx.strokeStyle = color;\n        ctx.stroke();\n        //end line shape\n\n        var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir);\n        if (link && link._pos) {\n            link._pos[0] = pos[0];\n            link._pos[1] = pos[1];\n        }\n\n        //render arrow in the middle\n        if (\n            this.ds.scale >= 0.6 &&\n            this.highquality_render &&\n            end_dir != LiteGraph.CENTER\n        ) {\n            //render arrow\n            if (this.render_connection_arrows) {\n                //compute two points in the connection\n                var posA = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.25,\n                    start_dir,\n                    end_dir\n                );\n                var posB = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.26,\n                    start_dir,\n                    end_dir\n                );\n                var posC = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.75,\n                    start_dir,\n                    end_dir\n                );\n                var posD = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.76,\n                    start_dir,\n                    end_dir\n                );\n\n                //compute the angle between them so the arrow points in the right direction\n                var angleA = 0;\n                var angleB = 0;\n                if (this.render_curved_connections) {\n                    angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]);\n                    angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]);\n                } else {\n                    angleB = angleA = b[1] > a[1] ? 0 : Math.PI;\n                }\n\n                //render arrow\n                ctx.save();\n                ctx.translate(posA[0], posA[1]);\n                ctx.rotate(angleA);\n                ctx.beginPath();\n                ctx.moveTo(-5, -3);\n                ctx.lineTo(0, +7);\n                ctx.lineTo(+5, -3);\n                ctx.fill();\n                ctx.restore();\n                ctx.save();\n                ctx.translate(posC[0], posC[1]);\n                ctx.rotate(angleB);\n                ctx.beginPath();\n                ctx.moveTo(-5, -3);\n                ctx.lineTo(0, +7);\n                ctx.lineTo(+5, -3);\n                ctx.fill();\n                ctx.restore();\n            }\n\n            //circle\n            ctx.beginPath();\n            ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2);\n            ctx.fill();\n        }\n\n        //render flowing points\n        if (flow) {\n            ctx.fillStyle = color;\n            for (var i = 0; i < 5; ++i) {\n                var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1;\n                var pos = this.computeConnectionPoint(\n                    a,\n                    b,\n                    f,\n                    start_dir,\n                    end_dir\n                );\n                ctx.beginPath();\n                ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI);\n                ctx.fill();\n            }\n        }\n    };\n\n    //returns the link center point based on curvature\n    LGraphCanvas.prototype.computeConnectionPoint = function(\n        a,\n        b,\n        t,\n        start_dir,\n        end_dir\n    ) {\n        start_dir = start_dir || LiteGraph.RIGHT;\n        end_dir = end_dir || LiteGraph.LEFT;\n\n        var dist = distance(a, b);\n        var p0 = a;\n        var p1 = [a[0], a[1]];\n        var p2 = [b[0], b[1]];\n        var p3 = b;\n\n        switch (start_dir) {\n            case LiteGraph.LEFT:\n                p1[0] += dist * -0.25;\n                break;\n            case LiteGraph.RIGHT:\n                p1[0] += dist * 0.25;\n                break;\n            case LiteGraph.UP:\n                p1[1] += dist * -0.25;\n                break;\n            case LiteGraph.DOWN:\n                p1[1] += dist * 0.25;\n                break;\n        }\n        switch (end_dir) {\n            case LiteGraph.LEFT:\n                p2[0] += dist * -0.25;\n                break;\n            case LiteGraph.RIGHT:\n                p2[0] += dist * 0.25;\n                break;\n            case LiteGraph.UP:\n                p2[1] += dist * -0.25;\n                break;\n            case LiteGraph.DOWN:\n                p2[1] += dist * 0.25;\n                break;\n        }\n\n        var c1 = (1 - t) * (1 - t) * (1 - t);\n        var c2 = 3 * ((1 - t) * (1 - t)) * t;\n        var c3 = 3 * (1 - t) * (t * t);\n        var c4 = t * t * t;\n\n        var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0];\n        var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1];\n        return [x, y];\n    };\n\n    LGraphCanvas.prototype.drawExecutionOrder = function(ctx) {\n        ctx.shadowColor = \"transparent\";\n        ctx.globalAlpha = 0.25;\n\n        ctx.textAlign = \"center\";\n        ctx.strokeStyle = \"white\";\n        ctx.globalAlpha = 0.75;\n\n        var visible_nodes = this.visible_nodes;\n        for (var i = 0; i < visible_nodes.length; ++i) {\n            var node = visible_nodes[i];\n            ctx.fillStyle = \"black\";\n            ctx.fillRect(\n                node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT,\n                node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT,\n                LiteGraph.NODE_TITLE_HEIGHT,\n                LiteGraph.NODE_TITLE_HEIGHT\n            );\n            if (node.order == 0) {\n                ctx.strokeRect(\n                    node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,\n                    node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,\n                    LiteGraph.NODE_TITLE_HEIGHT,\n                    LiteGraph.NODE_TITLE_HEIGHT\n                );\n            }\n            ctx.fillStyle = \"#FFF\";\n            ctx.fillText(\n                node.order,\n                node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5,\n                node.pos[1] - 6\n            );\n        }\n        ctx.globalAlpha = 1;\n    };\n\n    /**\n     * draws the widgets stored inside a node\n     * @method drawNodeWidgets\n     **/\n    LGraphCanvas.prototype.drawNodeWidgets = function(\n        node,\n        posY,\n        ctx,\n        active_widget\n    ) {\n        if (!node.widgets || !node.widgets.length) {\n            return 0;\n        }\n        var width = node.size[0];\n        var widgets = node.widgets;\n        posY += 2;\n        var H = LiteGraph.NODE_WIDGET_HEIGHT;\n        var show_text = this.ds.scale > 0.5;\n        ctx.save();\n        ctx.globalAlpha = this.editor_alpha;\n        var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR;\n        var background_color = LiteGraph.WIDGET_BGCOLOR;\n        var text_color = LiteGraph.WIDGET_TEXT_COLOR;\n\t\tvar secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;\n        var margin = 15;\n\n        for (var i = 0; i < widgets.length; ++i) {\n            var w = widgets[i];\n            var y = posY;\n            if (w.y) {\n                y = w.y;\n            }\n            w.last_y = y;\n            ctx.strokeStyle = outline_color;\n            ctx.fillStyle = \"#222\";\n            ctx.textAlign = \"left\";\n\t\t\t//ctx.lineWidth = 2;\n\t\t\tif(w.disabled)\n\t\t\t\tctx.globalAlpha *= 0.5;\n\t\t\tvar widget_width = w.width || width;\n\n            switch (w.type) {\n                case \"button\":\n                    if (w.clicked) {\n                        ctx.fillStyle = \"#AAA\";\n                        w.clicked = false;\n                        this.dirty_canvas = true;\n                    }\n                    ctx.fillRect(margin, y, widget_width - margin * 2, H);\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.strokeRect( margin, y, widget_width - margin * 2, H );\n                    if (show_text) {\n                        ctx.textAlign = \"center\";\n                        ctx.fillStyle = text_color;\n                        ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7);\n                    }\n                    break;\n                case \"toggle\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n                    if (show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]);\n\t\t\t\t\telse\n\t                    ctx.rect(margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.stroke();\n                    ctx.fillStyle = w.value ? \"#89A\" : \"#333\";\n                    ctx.beginPath();\n                    ctx.arc( widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2 );\n                    ctx.fill();\n                    if (show_text) {\n                        ctx.fillStyle = secondary_text_color;\n                        const label = w.label || w.name;    \n                        if (label != null) {\n                            ctx.fillText(label, margin * 2, y + H * 0.7);\n                        }\n                        ctx.fillStyle = w.value ? text_color : secondary_text_color;\n                        ctx.textAlign = \"right\";\n                        ctx.fillText(\n                            w.value\n                                ? w.options.on || \"true\"\n                                : w.options.off || \"false\",\n                            widget_width - 40,\n                            y + H * 0.7\n                        );\n                    }\n                    break;\n                case \"slider\":\n                    ctx.fillStyle = background_color;\n                    ctx.fillRect(margin, y, widget_width - margin * 2, H);\n                    var range = w.options.max - w.options.min;\n                    var nvalue = (w.value - w.options.min) / range;\n\t\t\t\t\tif(nvalue < 0.0) nvalue = 0.0;\n\t\t\t\t\tif(nvalue > 1.0) nvalue = 1.0;\n                    ctx.fillStyle = w.options.hasOwnProperty(\"slider_color\") ? w.options.slider_color : (active_widget == w ? \"#89A\" : \"#678\");\n                    ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H);\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.strokeRect(margin, y, widget_width - margin * 2, H);\n                    if (w.marker) {\n                        var marker_nvalue = (w.marker - w.options.min) / range;\n\t\t\t\t\t\tif(marker_nvalue < 0.0) marker_nvalue = 0.0;\n\t\t\t\t\t\tif(marker_nvalue > 1.0) marker_nvalue = 1.0;\n                        ctx.fillStyle = w.options.hasOwnProperty(\"marker_color\") ? w.options.marker_color : \"#AA9\";\n                        ctx.fillRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H );\n                    }\n                    if (show_text) {\n                        ctx.textAlign = \"center\";\n                        ctx.fillStyle = text_color;\n                        ctx.fillText(\n                            w.label || w.name + \"  \" + Number(w.value).toFixed(\n                                                            w.options.precision != null\n                                                                ? w.options.precision\n                                                                : 3\n                                                        ),\n                            widget_width * 0.5,\n                            y + H * 0.7\n                        );\n                    }\n                    break;\n                case \"number\":\n                case \"combo\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n\t\t\t\t\tif(show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5] );\n\t\t\t\t\telse\n\t                    ctx.rect(margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n                    if (show_text) {\n\t\t\t\t\t\tif(!w.disabled)\n\t\t                    ctx.stroke();\n                        ctx.fillStyle = text_color;\n\t\t\t\t\t\tif(!w.disabled)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\t\tctx.moveTo(margin + 16, y + 5);\n\t\t\t\t\t\t\tctx.lineTo(margin + 6, y + H * 0.5);\n\t\t\t\t\t\t\tctx.lineTo(margin + 16, y + H - 5);\n\t\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\t\tctx.moveTo(widget_width - margin - 16, y + 5);\n\t\t\t\t\t\t\tctx.lineTo(widget_width - margin - 6, y + H * 0.5);\n\t\t\t\t\t\t\tctx.lineTo(widget_width - margin - 16, y + H - 5);\n\t\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\t}\n                        ctx.fillStyle = secondary_text_color;\n                        ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7);\n                        ctx.fillStyle = text_color;\n                        ctx.textAlign = \"right\";\n                        if (w.type == \"number\") {\n                            ctx.fillText(\n                                Number(w.value).toFixed(\n                                    w.options.precision !== undefined\n                                        ? w.options.precision\n                                        : 3\n                                ),\n                                widget_width - margin * 2 - 20,\n                                y + H * 0.7\n                            );\n                        } else {\n\t\t\t\t\t\t\tvar v = w.value;\n\t\t\t\t\t\t\tif( w.options.values )\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tvar values = w.options.values;\n\t\t\t\t\t\t\t\tif( values.constructor === Function )\n\t\t\t\t\t\t\t\t\tvalues = values();\n\t\t\t\t\t\t\t\tif(values && values.constructor !== Array)\n\t\t\t\t\t\t\t\t\tv = values[ w.value ];\n\t\t\t\t\t\t\t}\n                            ctx.fillText(\n                                v,\n                                widget_width - margin * 2 - 20,\n                                y + H * 0.7\n                            );\n                        }\n                    }\n                    break;\n                case \"string\":\n                case \"text\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n                    if (show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]);\n\t\t\t\t\telse\n\t                    ctx.rect( margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n\t                if (show_text) {\n\t\t\t\t\t\tif(!w.disabled)\n\t\t\t\t\t\t\tctx.stroke();\n    \t\t\t\t\tctx.save();\n\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\tctx.rect(margin, y, widget_width - margin * 2, H);\n\t\t\t\t\t\tctx.clip();\n\n\t                    //ctx.stroke();\n                        ctx.fillStyle = secondary_text_color;\n                        const label = w.label || w.name;\t\n                        if (label != null) {\n                            ctx.fillText(label, margin * 2, y + H * 0.7);\n                        }\n                        ctx.fillStyle = text_color;\n                        ctx.textAlign = \"right\";\n                        ctx.fillText(String(w.value).substr(0,30), widget_width - margin * 2, y + H * 0.7); //30 chars max\n\t\t\t\t\t\tctx.restore();\n                    }\n                    break;\n                default:\n                    if (w.draw) {\n                        w.draw(ctx, node, widget_width, y, H);\n                    }\n                    break;\n            }\n            posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4;\n\t\t\tctx.globalAlpha = this.editor_alpha;\n\n        }\n        ctx.restore();\n\t\tctx.textAlign = \"left\";\n    };\n\n    /**\n     * process an event on widgets\n     * @method processNodeWidgets\n     **/\n    LGraphCanvas.prototype.processNodeWidgets = function(\n        node,\n        pos,\n        event,\n        active_widget\n    ) {\n        if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) {\n            return null;\n        }\n\n        var x = pos[0] - node.pos[0];\n        var y = pos[1] - node.pos[1];\n        var width = node.size[0];\n        var deltaX = event.deltaX || event.deltax || 0;\n        var that = this;\n        var ref_window = this.getCanvasWindow();\n\n        for (var i = 0; i < node.widgets.length; ++i) {\n            var w = node.widgets[i];\n\t\t\tif(!w || w.disabled)\n\t\t\t\tcontinue;\n\t\t\tvar widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT;\n\t\t\tvar widget_width = w.width || width;\n\t\t\t//outside\n\t\t\tif ( w != active_widget && \n\t\t\t\t(x < 6 || x > widget_width - 12 || y < w.last_y || y > w.last_y + widget_height || w.last_y === undefined) ) \n\t\t\t\tcontinue;\n\n\t\t\tvar old_value = w.value;\n\n            //if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) {\n\t\t\t//inside widget\n\t\t\tswitch (w.type) {\n\t\t\t\tcase \"button\":\n\t\t\t\t\tif (event.type === LiteGraph.pointerevents_method+\"down\") {\n                        if (w.callback) {\n                            setTimeout(function() {\n                                w.callback(w, that, node, pos, event);\n                            }, 20);\n                        }\n                        w.clicked = true;\n                        this.dirty_canvas = true;\n                    }\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"slider\":\n\t\t\t\t\tvar old_value = w.value;\n\t\t\t\t\tvar nvalue = clamp((x - 15) / (widget_width - 30), 0, 1);\n\t\t\t\t\tif(w.options.read_only) break;\n\t\t\t\t\tw.value = w.options.min + (w.options.max - w.options.min) * nvalue;\n\t\t\t\t\tif (old_value != w.value) {\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tinner_value_change(w, w.value);\n\t\t\t\t\t\t}, 20);\n\t\t\t\t\t}\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"number\":\n\t\t\t\tcase \"combo\":\n\t\t\t\t\tvar old_value = w.value;\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"move\" && w.type == \"number\") {\n                        if(deltaX)\n\t\t\t\t\t\t    w.value += deltaX * 0.1 * (w.options.step || 1);\n\t\t\t\t\t\tif ( w.options.min != null && w.value < w.options.min ) {\n\t\t\t\t\t\t\tw.value = w.options.min;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( w.options.max != null && w.value > w.options.max ) {\n\t\t\t\t\t\t\tw.value = w.options.max;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tvar values = w.options.values;\n\t\t\t\t\t\tif (values && values.constructor === Function) {\n\t\t\t\t\t\t\tvalues = w.options.values(w, node);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar values_list = null;\n\t\t\t\t\t\t\n\t\t\t\t\t\tif( w.type != \"number\")\n\t\t\t\t\t\t\tvalues_list = values.constructor === Array ? values : Object.keys(values);\n\n\t\t\t\t\t\tvar delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0;\n\t\t\t\t\t\tif (w.type == \"number\") {\n\t\t\t\t\t\t\tw.value += delta * 0.1 * (w.options.step || 1);\n\t\t\t\t\t\t\tif ( w.options.min != null && w.value < w.options.min ) {\n\t\t\t\t\t\t\t\tw.value = w.options.min;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ( w.options.max != null && w.value > w.options.max ) {\n\t\t\t\t\t\t\t\tw.value = w.options.max;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (delta) { //clicked in arrow, used for combos \n\t\t\t\t\t\t\tvar index = -1;\n\t\t\t\t\t\t\tthis.last_mouseclick = 0; //avoids dobl click event\n\t\t\t\t\t\t\tif(values.constructor === Object)\n\t\t\t\t\t\t\t\tindex = values_list.indexOf( String( w.value ) ) + delta;\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tindex = values_list.indexOf( w.value ) + delta;\n\t\t\t\t\t\t\tif (index >= values_list.length) {\n\t\t\t\t\t\t\t\tindex = values_list.length - 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (index < 0) {\n\t\t\t\t\t\t\t\tindex = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif( values.constructor === Array )\n\t\t\t\t\t\t\t\tw.value = values[index];\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tw.value = index;\n\t\t\t\t\t\t} else { //combo clicked \n\t\t\t\t\t\t\tvar text_values = values != values_list ? Object.values(values) : values;\n\t\t\t\t\t\t\tvar menu = new LiteGraph.ContextMenu(text_values, {\n\t\t\t\t\t\t\t\t\tscale: Math.max(1, this.ds.scale),\n\t\t\t\t\t\t\t\t\tevent: event,\n\t\t\t\t\t\t\t\t\tclassName: \"dark\",\n\t\t\t\t\t\t\t\t\tcallback: inner_clicked.bind(w)\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tref_window);\n\t\t\t\t\t\t\tfunction inner_clicked(v, option, event) {\n\t\t\t\t\t\t\t\tif(values != values_list)\n\t\t\t\t\t\t\t\t\tv = text_values.indexOf(v);\n\t\t\t\t\t\t\t\tthis.value = v;\n\t\t\t\t\t\t\t\tinner_value_change(this, v);\n\t\t\t\t\t\t\t\tthat.dirty_canvas = true;\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} //end mousedown\n\t\t\t\t\telse if(event.type == LiteGraph.pointerevents_method+\"up\" && w.type == \"number\")\n\t\t\t\t\t{\n\t\t\t\t\t\tvar delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0;\n\t\t\t\t\t\tif (event.click_time < 200 && delta == 0) {\n\t\t\t\t\t\t\tthis.prompt(\"Value\",w.value,function(v) {\n\t\t\t\t\t\t\t\t\t// check if v is a valid equation or a number\n\t\t\t\t\t\t\t\t\t  if (/^[0-9+\\-*/()\\s]+|\\d+\\.\\d+$/.test(v)) {\n\t\t\t\t\t\t\t\t\t\ttry {//solve the equation if possible\n\t\t\t\t\t\t\t\t\t    \t\tv = eval(v);\n\t\t\t\t\t\t\t\t\t\t} catch (e) { }\n\t\t\t\t\t\t\t\t\t}\t\n\t\t\t\t\t\t\t\t\tthis.value = Number(v);\n\t\t\t\t\t\t\t\t\tinner_value_change(this, this.value);\n\t\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\t\tevent);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif( old_value != w.value )\n\t\t\t\t\t\tsetTimeout(\n\t\t\t\t\t\t\tfunction() {\n\t\t\t\t\t\t\t\tinner_value_change(this, this.value);\n\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\t20\n\t\t\t\t\t\t);\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"toggle\":\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tw.value = !w.value;\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tinner_value_change(w, w.value);\n\t\t\t\t\t\t}, 20);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"string\":\n\t\t\t\tcase \"text\":\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tthis.prompt(\"Value\",w.value,function(v) {\n\t\t\t\t\t\t\t\tinner_value_change(this, v);\n\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\tevent,w.options ? w.options.multiline : false );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tif (w.mouse) {\n\t\t\t\t\t\tthis.dirty_canvas = w.mouse(event, [x, y], node);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t} //end switch\n\n\t\t\t//value changed\n\t\t\tif( old_value != w.value )\n\t\t\t{\n\t\t\t\tif(node.onWidgetChanged)\n\t\t\t\t\tnode.onWidgetChanged( w.name,w.value,old_value,w );\n                node.graph._version++;\n\t\t\t}\n\n\t\t\treturn w;\n        }//end for\n\n        function inner_value_change(widget, value) {\n            if(widget.type == \"number\"){\n                value = Number(value);\n            }\n            widget.value = value;\n            if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) {\n                node.setProperty( widget.options.property, value );\n            }\n            if (widget.callback) {\n                widget.callback(widget.value, that, node, pos, event);\n            }\n        }\n\n        return null;\n    };\n\n    /**\n     * draws every group area in the background\n     * @method drawGroups\n     **/\n    LGraphCanvas.prototype.drawGroups = function(canvas, ctx) {\n        if (!this.graph) {\n            return;\n        }\n\n        var groups = this.graph._groups;\n\n        ctx.save();\n        ctx.globalAlpha = 0.5 * this.editor_alpha;\n\n        for (var i = 0; i < groups.length; ++i) {\n            var group = groups[i];\n\n            if (!overlapBounding(this.visible_area, group._bounding)) {\n                continue;\n            } //out of the visible area\n\n            ctx.fillStyle = group.color || \"#335\";\n            ctx.strokeStyle = group.color || \"#335\";\n            var pos = group._pos;\n            var size = group._size;\n            ctx.globalAlpha = 0.25 * this.editor_alpha;\n            ctx.beginPath();\n            ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]);\n            ctx.fill();\n            ctx.globalAlpha = this.editor_alpha;\n            ctx.stroke();\n\n            ctx.beginPath();\n            ctx.moveTo(pos[0] + size[0], pos[1] + size[1]);\n            ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]);\n            ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10);\n            ctx.fill();\n\n            var font_size =\n                group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;\n            ctx.font = font_size + \"px Arial\";\n\t\t\tctx.textAlign = \"left\";\n            ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size);\n        }\n\n        ctx.restore();\n    };\n\n    LGraphCanvas.prototype.adjustNodesSize = function() {\n        var nodes = this.graph._nodes;\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].size = nodes[i].computeSize();\n        }\n        this.setDirty(true, true);\n    };\n\n    /**\n     * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode\n     * @method resize\n     **/\n    LGraphCanvas.prototype.resize = function(width, height) {\n        if (!width && !height) {\n            var parent = this.canvas.parentNode;\n            width = parent.offsetWidth;\n            height = parent.offsetHeight;\n        }\n\n        if (this.canvas.width == width && this.canvas.height == height) {\n            return;\n        }\n\n        this.canvas.width = width;\n        this.canvas.height = height;\n        this.bgcanvas.width = this.canvas.width;\n        this.bgcanvas.height = this.canvas.height;\n        this.setDirty(true, true);\n    };\n\n    /**\n     * switches to live mode (node shapes are not rendered, only the content)\n     * this feature was designed when graphs where meant to create user interfaces\n     * @method switchLiveMode\n     **/\n    LGraphCanvas.prototype.switchLiveMode = function(transition) {\n        if (!transition) {\n            this.live_mode = !this.live_mode;\n            this.dirty_canvas = true;\n            this.dirty_bgcanvas = true;\n            return;\n        }\n\n        var self = this;\n        var delta = this.live_mode ? 1.1 : 0.9;\n        if (this.live_mode) {\n            this.live_mode = false;\n            this.editor_alpha = 0.1;\n        }\n\n        var t = setInterval(function() {\n            self.editor_alpha *= delta;\n            self.dirty_canvas = true;\n            self.dirty_bgcanvas = true;\n\n            if (delta < 1 && self.editor_alpha < 0.01) {\n                clearInterval(t);\n                if (delta < 1) {\n                    self.live_mode = true;\n                }\n            }\n            if (delta > 1 && self.editor_alpha > 0.99) {\n                clearInterval(t);\n                self.editor_alpha = 1;\n            }\n        }, 1);\n    };\n\n    LGraphCanvas.prototype.onNodeSelectionChange = function(node) {\n        return; //disabled\n    };\n\n    /* this is an implementation for touch not in production and not ready\n     */\n    /*LGraphCanvas.prototype.touchHandler = function(event) {\n        //alert(\"foo\");\n        var touches = event.changedTouches,\n            first = touches[0],\n            type = \"\";\n\n        switch (event.type) {\n            case \"touchstart\":\n                type = \"mousedown\";\n                break;\n            case \"touchmove\":\n                type = \"mousemove\";\n                break;\n            case \"touchend\":\n                type = \"mouseup\";\n                break;\n            default:\n                return;\n        }\n\n        //initMouseEvent(type, canBubble, cancelable, view, clickCount,\n        //           screenX, screenY, clientX, clientY, ctrlKey,\n        //           altKey, shiftKey, metaKey, button, relatedTarget);\n\n        // this is eventually a Dom object, get the LGraphCanvas back\n        if(typeof this.getCanvasWindow == \"undefined\"){\n            var window = this.lgraphcanvas.getCanvasWindow();\n        }else{\n            var window = this.getCanvasWindow();\n        }\n        \n        var document = window.document;\n\n        var simulatedEvent = document.createEvent(\"MouseEvent\");\n        simulatedEvent.initMouseEvent(\n            type,\n            true,\n            true,\n            window,\n            1,\n            first.screenX,\n            first.screenY,\n            first.clientX,\n            first.clientY,\n            false,\n            false,\n            false,\n            false,\n            0, //left\n            null\n        );\n        first.target.dispatchEvent(simulatedEvent);\n        event.preventDefault();\n    };*/\n\n    /* CONTEXT MENU ********************/\n\n    LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) {\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var group = new LiteGraph.LGraphGroup();\n        group.pos = canvas.convertEventToCanvasOffset(mouse_event);\n        canvas.graph.add(group);\n    };\n\n    /**\n     * Determines the furthest nodes in each direction\n     * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted\n     * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}\n     */\n    LGraphCanvas.getBoundaryNodes = function(nodes) {\n        let top = null;\n        let right = null;\n        let bottom = null;\n        let left = null;\n        for (const nID in nodes) {\n            const node = nodes[nID];\n            const [x, y] = node.pos;\n            const [width, height] = node.size;\n\n            if (top === null || y < top.pos[1]) {\n                top = node;\n            }\n            if (right === null || x + width > right.pos[0] + right.size[0]) {\n                right = node;\n            }\n            if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) {\n                bottom = node;\n            }\n            if (left === null || x < left.pos[0]) {\n                left = node;\n            }\n        }\n\n        return {\n            \"top\": top,\n            \"right\": right,\n            \"bottom\": bottom,\n            \"left\": left\n        };\n    }\n    /**\n     * Determines the furthest nodes in each direction for the currently selected nodes\n     * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}\n     */\n    LGraphCanvas.prototype.boundaryNodesForSelection = function() {\n        return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes));\n    }\n\n    /**\n     *\n     * @param {LGraphNode[]} nodes a list of nodes\n     * @param {\"top\"|\"bottom\"|\"left\"|\"right\"} direction Direction to align the nodes\n     * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction)\n     */\n    LGraphCanvas.alignNodes = function (nodes, direction, align_to) {\n        if (!nodes) {\n            return;\n        }\n\n        const canvas = LGraphCanvas.active_canvas;\n        let boundaryNodes = []\n        if (align_to === undefined) {\n            boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes)\n        } else {\n            boundaryNodes = {\n                \"top\": align_to,\n                \"right\": align_to,\n                \"bottom\": align_to,\n                \"left\": align_to\n            }\n        }\n\n        for (const [_, node] of Object.entries(canvas.selected_nodes)) {\n            switch (direction) {\n                case \"right\":\n                    node.pos[0] = boundaryNodes[\"right\"].pos[0] + boundaryNodes[\"right\"].size[0] - node.size[0];\n                    break;\n                case \"left\":\n                    node.pos[0] = boundaryNodes[\"left\"].pos[0];\n                    break;\n                case \"top\":\n                    node.pos[1] = boundaryNodes[\"top\"].pos[1];\n                    break;\n                case \"bottom\":\n                    node.pos[1] = boundaryNodes[\"bottom\"].pos[1] + boundaryNodes[\"bottom\"].size[1] - node.size[1];\n                    break;\n            }\n        }\n\n        canvas.dirty_canvas = true;\n        canvas.dirty_bgcanvas = true;\n    };\n\n    LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) {\n        new LiteGraph.ContextMenu([\"Top\", \"Bottom\", \"Left\", \"Right\"], {\n            event: event,\n            callback: inner_clicked,\n            parentMenu: prev_menu,\n        });\n\n        function inner_clicked(value) {\n            LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node);\n        }\n    }\n\n    LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) {\n        new LiteGraph.ContextMenu([\"Top\", \"Bottom\", \"Left\", \"Right\"], {\n            event: event,\n            callback: inner_clicked,\n            parentMenu: prev_menu,\n        });\n\n        function inner_clicked(value) {\n            LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase());\n        }\n    }\n\n    LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) {\n\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n        var graph = canvas.graph;\n        if (!graph)\n            return;\n\n        function inner_onMenuAdded(base_category ,prev_menu){\n    \n            var categories  = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function(category){return category.startsWith(base_category)});\n            var entries = [];\n    \n            categories.map(function(category){\n    \n                if (!category) \n                    return;\n    \n                var base_category_regex = new RegExp('^(' + base_category + ')');\n                var category_name = category.replace(base_category_regex,\"\").split('/')[0];\n                var category_path = base_category  === '' ? category_name + '/' : base_category + category_name + '/';\n    \n                var name = category_name;\n                if(name.indexOf(\"::\") != -1) //in case it has a namespace like \"shader::math/rand\" it hides the namespace\n                    name = name.split(\"::\")[1];\n                        \n                var index = entries.findIndex(function(entry){return entry.value === category_path});\n                if (index === -1) {\n                    entries.push({ value: category_path, content: name, has_submenu: true, callback : function(value, event, mouseEvent, contextMenu){\n                        inner_onMenuAdded(value.value, contextMenu)\n                    }});\n                }\n                \n            });\n    \n            var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter );\n            nodes.map(function(node){\n    \n                if (node.skip_list)\n                    return;\n    \n                var entry = { value: node.type, content: node.title, has_submenu: false , callback : function(value, event, mouseEvent, contextMenu){\n                    \n                        var first_event = contextMenu.getFirstEvent();\n                        canvas.graph.beforeChange();\n                        var node = LiteGraph.createNode(value.value);\n                        if (node) {\n                            node.pos = canvas.convertEventToCanvasOffset(first_event);\n                            canvas.graph.add(node);\n                        }\n                        if(callback)\n                            callback(node);\n                        canvas.graph.afterChange();\n                    \n                    }\n                }\n    \n                entries.push(entry);\n    \n            });\n    \n            new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window );\n    \n        }\n    \n        inner_onMenuAdded('',prev_menu);\n        return false;\n    \n    };\n\n    LGraphCanvas.onMenuCollapseAll = function() {};\n\n    LGraphCanvas.onMenuNodeEdit = function() {};\n\n    LGraphCanvas.showMenuNodeOptionalInputs = function(\n        v,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var options = node.optional_inputs;\n        if (node.onGetInputs) {\n            options = node.onGetInputs();\n        }\n\n        var entries = [];\n        if (options) {\n            for (var i=0; i < options.length; i++) {\n                var entry = options[i];\n                if (!entry) {\n                    entries.push(null);\n                    continue;\n                }\n                var label = entry[0];\n\t\t\t\tif(!entry[2])\n\t\t\t\t\tentry[2] = {};\n\n                if (entry[2].label) {\n                    label = entry[2].label;\n                }\n\n\t\t\t\tentry[2].removable = true;\n                var data = { content: label, value: entry };\n                if (entry[1] == LiteGraph.ACTION) {\n                    data.className = \"event\";\n                }\n                entries.push(data);\n            }\n        }\n\n        if (node.onMenuNodeInputs) {\n            var retEntries = node.onMenuNodeInputs(entries);\n            if(retEntries) entries = retEntries;\n        }\n\n        if (!entries.length) {\n\t\t\tconsole.log(\"no input entries\");\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, e, prev) {\n            if (!node) {\n                return;\n            }\n\n            if (v.callback) {\n                v.callback.call(that, node, v, e, prev);\n            }\n\n            if (v.value) {\n\t\t\t\tnode.graph.beforeChange();\n                node.addInput(v.value[0], v.value[1], v.value[2]);\n\n                if (node.onNodeInputAdd) { // callback to the node when adding a slot\n                    node.onNodeInputAdd(v.value);\n                }\n                node.setDirtyCanvas(true, true);\n\t\t\t\tnode.graph.afterChange();\n            }\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.showMenuNodeOptionalOutputs = function(\n        v,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var options = node.optional_outputs;\n        if (node.onGetOutputs) {\n            options = node.onGetOutputs();\n        }\n\n        var entries = [];\n        if (options) {\n            for (var i=0; i < options.length; i++) {\n                var entry = options[i];\n                if (!entry) {\n                    //separator?\n                    entries.push(null);\n                    continue;\n                }\n\n                if (\n                    node.flags &&\n                    node.flags.skip_repeated_outputs &&\n                    node.findOutputSlot(entry[0]) != -1\n                ) {\n                    continue;\n                } //skip the ones already on\n                var label = entry[0];\n\t\t\t\tif(!entry[2])\n\t\t\t\t\tentry[2] = {};\n                if (entry[2].label) {\n                    label = entry[2].label;\n                }\n\t\t\t\tentry[2].removable = true;\n                var data = { content: label, value: entry };\n                if (entry[1] == LiteGraph.EVENT) {\n                    data.className = \"event\";\n                }\n                entries.push(data);\n            }\n        }\n\n        if (this.onMenuNodeOutputs) {\n            entries = this.onMenuNodeOutputs(entries);\n        }\n        if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted\n            if (node.findOutputSlot(\"onExecuted\") == -1){\n                entries.push({content: \"On Executed\", value: [\"onExecuted\", LiteGraph.EVENT, {nameLocked: true}], className: \"event\"}); //, opts: {}\n            }\n        }\n        // add callback for modifing the menu elements onMenuNodeOutputs\n        if (node.onMenuNodeOutputs) {\n            var retEntries = node.onMenuNodeOutputs(entries);\n            if(retEntries) entries = retEntries;\n        }\n\n        if (!entries.length) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, e, prev) {\n            if (!node) {\n                return;\n            }\n\n            if (v.callback) {\n                v.callback.call(that, node, v, e, prev);\n            }\n\n            if (!v.value) {\n                return;\n            }\n\n            var value = v.value[1];\n\n            if (\n                value &&\n                (value.constructor === Object || value.constructor === Array)\n            ) {\n                //submenu why?\n                var entries = [];\n                for (var i in value) {\n                    entries.push({ content: i, value: value[i] });\n                }\n                new LiteGraph.ContextMenu(entries, {\n                    event: e,\n                    callback: inner_clicked,\n                    parentMenu: prev_menu,\n                    node: node\n                });\n                return false;\n            } else {\n\t\t\t\tnode.graph.beforeChange();\n                node.addOutput(v.value[0], v.value[1], v.value[2]);\n\n                if (node.onNodeOutputAdd) { // a callback to the node when adding a slot\n                    node.onNodeOutputAdd(v.value);\n                }\n                node.setDirtyCanvas(true, true);\n\t\t\t\tnode.graph.afterChange();\n            }\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onShowMenuNodeProperties = function(\n        value,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node || !node.properties) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var entries = [];\n        for (var i in node.properties) {\n            var value = node.properties[i] !== undefined ? node.properties[i] : \" \";\n\t\t\tif( typeof value == \"object\" )\n\t\t\t\tvalue = JSON.stringify(value);\n\t\t\tvar info = node.getPropertyInfo(i);\n\t\t\tif(info.type == \"enum\" || info.type == \"combo\")\n\t\t\t\tvalue = LGraphCanvas.getPropertyPrintableValue( value, info.values );\n\n            //value could contain invalid html characters, clean that\n            value = LGraphCanvas.decodeHTML(value);\n            entries.push({\n                content:\n                    \"<span class='property_name'>\" +\n                    (info.label ? info.label : i) +\n                    \"</span>\" +\n                    \"<span class='property_value'>\" +\n                    value +\n                    \"</span>\",\n                value: i\n            });\n        }\n        if (!entries.length) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                allow_html: true,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, options, e, prev) {\n            if (!node) {\n                return;\n            }\n            var rect = this.getBoundingClientRect();\n            canvas.showEditPropertyValue(node, v.value, {\n                position: [rect.left, rect.top]\n            });\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.decodeHTML = function(str) {\n        var e = document.createElement(\"div\");\n        e.innerText = str;\n        return e.innerHTML;\n    };\n\n    LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) {\n        if (!node) {\n            return;\n        }\n        \n\t\tvar fApplyMultiNode = function(node){\n\t\t\tnode.size = node.computeSize();\n\t\t\tif (node.onResize)\n\t\t\t\tnode.onResize(node.size);\n\t\t}\n\t\t\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.prototype.showLinkMenu = function(link, e) {\n        var that = this;\n\t\t// console.log(link);\n\t\tvar node_left = that.graph.getNodeById( link.origin_id );\n\t\tvar node_right = that.graph.getNodeById( link.target_id );\n\t\tvar fromType = false;\n\t\tif (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type;\n        var destType = false;\n\t\tif (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type;\n\t\t\n\t\tvar options = [\"Add Node\",null,\"Delete\",null];\n\t\t\n\t\t\n        var menu = new LiteGraph.ContextMenu(options, {\n            event: e,\n\t\t\ttitle: link.data != null ? link.data.constructor.name : null,\n            callback: inner_clicked\n        });\n\n        function inner_clicked(v,options,e) {\n            switch (v) {\n                case \"Add Node\":\n\t\t\t\t\tLGraphCanvas.onMenuAdd(null, null, e, menu, function(node){\n\t\t\t\t\t\t// console.debug(\"node autoconnect\");\n\t\t\t\t\t\tif(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length){\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// leave the connection type checking inside connectByType\n\t\t\t\t\t\tif (node_left.connectByType( link.origin_slot, node, fromType )){\n                        \tnode.connectByType( link.target_slot, node_right, destType );\n                            node.pos[0] -= node.size[0] * 0.5;\n                        }\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t\t\n                case \"Delete\":\n                    that.graph.removeLink(link.id);\n                    break;\n                default:\n\t\t\t\t\t/*var nodeCreated = createDefaultNodeForSlot({   nodeFrom: node_left\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotFrom: link.origin_slot\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeTo: node\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotTo: link.target_slot\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,e: e\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: \"AUTO\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\tif(nodeCreated) console.log(\"new node in beetween \"+v+\" created\");*/\n            }\n        }\n\n        return false;\n    };\n    \n \tLGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection\n        var optPass = optPass || {};\n        var opts = Object.assign({   nodeFrom: null // input\n                                    ,slotFrom: null // input\n                                    ,nodeTo: null   // output\n                                    ,slotTo: null   // output\n                                    ,position: []\t// pass the event coords\n\t\t\t\t\t\t\t\t  \t,nodeType: null\t// choose a nodetype to add, AUTO to set at first good\n\t\t\t\t\t\t\t\t  \t,posAdd:[0,0]\t// adjust x,y\n\t\t\t\t\t\t\t\t  \t,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h\n                                }\n                                ,optPass\n                            );\n        var that = this;\n        \n        var isFrom = opts.nodeFrom && opts.slotFrom!==null;\n        var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null;\n\t\n        if (!isFrom && !isTo){\n            console.warn(\"No data passed to createDefaultNodeForSlot \"+opts.nodeFrom+\" \"+opts.slotFrom+\" \"+opts.nodeTo+\" \"+opts.slotTo);\n            return false;\n        }\n\t\tif (!opts.nodeType){\n            console.warn(\"No type to createDefaultNodeForSlot\");\n            return false;\n        }\n        \n        var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;\n        var slotX = isFrom ? opts.slotFrom : opts.slotTo;\n        \n        var iSlotConn = false;\n        switch (typeof slotX){\n            case \"string\":\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            case \"object\":\n                // ok slotX\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);\n            break;\n            case \"number\":\n                iSlotConn = slotX;\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n\t\t\tcase \"undefined\":\n            default:\n                // bad ?\n                //iSlotConn = 0;\n                console.warn(\"Cant get slot information \"+slotX);\n                return false;\n        }\n\t\n\t\tif (slotX===false || iSlotConn===false){\n\t\t\tconsole.warn(\"createDefaultNodeForSlot bad slotX \"+slotX+\" \"+iSlotConn);\n\t\t}\n\t\t\n\t\t// check for defaults nodes for this slottype\n\t\tvar fromSlotType = slotX.type==LiteGraph.EVENT?\"_event_\":slotX.type;\n\t\tvar slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;\n\t\tif(slotTypesDefault && slotTypesDefault[fromSlotType]){\n\t\t\tif (slotX.link !== null) {\n\t\t\t\t// is connected\n\t\t\t}else{\n\t\t\t\t// is not not connected\n\t\t\t}\n\t\t\tnodeNewType = false;\n\t\t\tif(typeof slotTypesDefault[fromSlotType] == \"object\" || typeof slotTypesDefault[fromSlotType] == \"array\"){\n\t\t\t\tfor(var typeX in slotTypesDefault[fromSlotType]){\n\t\t\t\t\tif (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == \"AUTO\"){\n\t\t\t\t\t\tnodeNewType = slotTypesDefault[fromSlotType][typeX];\n\t\t\t\t\t\t// console.log(\"opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: \"+opts.nodeType);\n\t\t\t\t\t\tbreak; // --------\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\tif (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == \"AUTO\") nodeNewType = slotTypesDefault[fromSlotType];\n\t\t\t}\n\t\t\tif (nodeNewType) {\n\t\t\t\tvar nodeNewOpts = false;\n\t\t\t\tif (typeof nodeNewType == \"object\" && nodeNewType.node){\n\t\t\t\t\tnodeNewOpts = nodeNewType;\n\t\t\t\t\tnodeNewType = nodeNewType.node;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t//that.graph.beforeChange();\n\t\t\t\t\n\t\t\t\tvar newNode = LiteGraph.createNode(nodeNewType);\n\t\t\t\tif(newNode){\n\t\t\t\t\t// if is object pass options\n\t\t\t\t\tif (nodeNewOpts){\n\t\t\t\t\t\tif (nodeNewOpts.properties) {\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.properties) {\n\t\t\t\t\t\t\t\tnewNode.addProperty( i, nodeNewOpts.properties[i] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.inputs) {\n\t\t\t\t\t\t\tnewNode.inputs = [];\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.inputs) {\n\t\t\t\t\t\t\t\tnewNode.addOutput(\n\t\t\t\t\t\t\t\t\tnodeNewOpts.inputs[i][0],\n\t\t\t\t\t\t\t\t\tnodeNewOpts.inputs[i][1]\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.outputs) {\n\t\t\t\t\t\t\tnewNode.outputs = [];\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.outputs) {\n\t\t\t\t\t\t\t\tnewNode.addOutput(\n\t\t\t\t\t\t\t\t\tnodeNewOpts.outputs[i][0],\n\t\t\t\t\t\t\t\t\tnodeNewOpts.outputs[i][1]\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.title) {\n\t\t\t\t\t\t\tnewNode.title = nodeNewOpts.title;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.json) {\n\t\t\t\t\t\t\tnewNode.configure(nodeNewOpts.json);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// add the node\n\t\t\t\t\tthat.graph.add(newNode);\n\t\t\t\t\tnewNode.pos = [\topts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0)\n\t\t\t\t\t\t\t\t   \t,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/\n\t\t\t\t\t\n\t\t\t\t\t//that.graph.afterChange();\n\t\t\t\t\t\n\t\t\t\t\t// connect the two!\n\t\t\t\t\tif (isFrom){\n\t\t\t\t\t\topts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType );\n\t\t\t\t\t}else{\n\t\t\t\t\t\topts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType );\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// if connecting in between\n\t\t\t\t\tif (isFrom && isTo){\n\t\t\t\t\t\t// TODO\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treturn true;\n\t\t\t\t\t\n\t\t\t\t}else{\n\t\t\t\t\tconsole.log(\"failed creating \"+nodeNewType);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n \n    LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection\n        var optPass = optPass || {};\n        var opts = Object.assign({   nodeFrom: null  // input\n                                    ,slotFrom: null // input\n                                    ,nodeTo: null   // output\n                                    ,slotTo: null   // output\n                                    ,e: null\n                                }\n                                ,optPass\n                            );\n        var that = this;\n        \n        var isFrom = opts.nodeFrom && opts.slotFrom;\n        var isTo = !isFrom && opts.nodeTo && opts.slotTo;\n        \n        if (!isFrom && !isTo){\n            console.warn(\"No data passed to showConnectionMenu\");\n            return false;\n        }\n        \n        var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;\n        var slotX = isFrom ? opts.slotFrom : opts.slotTo;\n        \n        var iSlotConn = false;\n        switch (typeof slotX){\n            case \"string\":\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            case \"object\":\n                // ok slotX\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);\n            break;\n            case \"number\":\n                iSlotConn = slotX;\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            default:\n                // bad ?\n                //iSlotConn = 0;\n                console.warn(\"Cant get slot information \"+slotX);\n                return false;\n        }\n            \n\t\tvar options = [\"Add Node\",null];\n\t\t\n\t\tif (that.allow_searchbox){\n\t\t\toptions.push(\"Search\");\n\t\t\toptions.push(null);\n\t\t}\n\t\t\n\t\t// get defaults nodes for this slottype\n\t\tvar fromSlotType = slotX.type==LiteGraph.EVENT?\"_event_\":slotX.type;\n\t\tvar slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;\n\t\tif(slotTypesDefault && slotTypesDefault[fromSlotType]){\n\t\t\tif(typeof slotTypesDefault[fromSlotType] == \"object\" || typeof slotTypesDefault[fromSlotType] == \"array\"){\n\t\t\t\tfor(var typeX in slotTypesDefault[fromSlotType]){\n\t\t\t\t\toptions.push(slotTypesDefault[fromSlotType][typeX]);\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\toptions.push(slotTypesDefault[fromSlotType]);\n\t\t\t}\n\t\t}\n\t\t\n\t\t// build menu\n        var menu = new LiteGraph.ContextMenu(options, {\n            event: opts.e,\n\t\t\ttitle: (slotX && slotX.name!=\"\" ? (slotX.name + (fromSlotType?\" | \":\"\")) : \"\")+(slotX && fromSlotType ? fromSlotType : \"\"),\n            callback: inner_clicked\n        });\n        \n\t\t// callback\n        function inner_clicked(v,options,e) {\n            //console.log(\"Process showConnectionMenu selection\");\n            switch (v) {\n                case \"Add Node\":\n                    LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){\n                        if (isFrom){\n                            opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType );\n                        }else{\n                            opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType );\n                        }\n                    });\n                    break;\n\t\t\t\tcase \"Search\":\n\t\t\t\t\tif(isFrom){\n\t\t\t\t\t\tthat.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType});\n\t\t\t\t\t}else{\n\t\t\t\t\t\tthat.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType});\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n                default:\n\t\t\t\t\t// check for defaults nodes for this slottype\n\t\t\t\t\tvar nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: v\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\tif (nodeCreated){\n\t\t\t\t\t\t// new node created\n\t\t\t\t\t\t//console.log(\"node \"+v+\" created\")\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// failed or v is not in defaults\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n            }\n        }   \n        \n        return false;\n    };\n\n    // TODO refactor :: this is used fot title but not for properties!\n    LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) {\n        var input_html = \"\";\n        var property = item.property || \"title\";\n        var value = node[property];\n\n        // TODO refactor :: use createDialog ?\n        \n        var dialog = document.createElement(\"div\");\n        dialog.is_modified = false;\n        dialog.className = \"graphdialog\";\n        dialog.innerHTML =\n            \"<span class='name'></span><input autofocus type='text' class='value'/><button>OK</button>\";\n        dialog.close = function() {\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n        var title = dialog.querySelector(\".name\");\n        title.innerText = property;\n        var input = dialog.querySelector(\".value\");\n        if (input) {\n            input.value = value;\n            input.addEventListener(\"blur\", function(e) {\n                this.focus();\n            });\n            input.addEventListener(\"keydown\", function(e) {\n                dialog.is_modified = true;\n                if (e.keyCode == 27) {\n                    //ESC\n                    dialog.close();\n                } else if (e.keyCode == 13) {\n                    inner(); // save\n                } else if (e.keyCode != 13 && e.target.localName != \"textarea\") {\n                    return;\n                }\n                e.preventDefault();\n                e.stopPropagation();\n            });\n        }\n\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n\n        var rect = canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", inner);\n        canvas.parentNode.appendChild(dialog);\n\n        if(input) input.focus();\n        \n        var dialogCloseTimer = null;\n        dialog.addEventListener(\"mouseleave\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        dialog.addEventListener(\"mouseenter\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        \n        function inner() {\n            if(input) setValue(input.value);\n        }\n\n        function setValue(value) {\n            if (item.type == \"Number\") {\n                value = Number(value);\n            } else if (item.type == \"Boolean\") {\n                value = Boolean(value);\n            }\n            node[property] = value;\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n            node.setDirtyCanvas(true, true);\n        }\n    };\n\n    // refactor: there are different dialogs, some uses createDialog some dont\n    LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) {\n        var that = this;\n        var input_html = \"\";\n        title = title || \"\";\n\n        var dialog = document.createElement(\"div\");\n        dialog.is_modified = false;\n        dialog.className = \"graphdialog rounded\";\n        if(multiline)\n\t        dialog.innerHTML = \"<span class='name'></span> <textarea autofocus class='value'></textarea><button class='rounded'>OK</button>\";\n\t\telse\n        \tdialog.innerHTML = \"<span class='name'></span> <input autofocus type='text' class='value'/><button class='rounded'>OK</button>\";\n        dialog.close = function() {\n            that.prompt_box = null;\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n        canvas.parentNode.appendChild(dialog);\n        \n        if (this.ds.scale > 1) {\n            dialog.style.transform = \"scale(\" + this.ds.scale + \")\";\n        }\n\n        var dialogCloseTimer = null;\n        var prevent_timeout = false;\n        LiteGraph.pointerListenerAdd(dialog,\"leave\", function(e) {\n            if (prevent_timeout)\n                return;\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        LiteGraph.pointerListenerAdd(dialog,\"enter\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        var selInDia = dialog.querySelectorAll(\"select\");\n        if (selInDia){\n            // if filtering, check focus changed to comboboxes and prevent closing\n            selInDia.forEach(function(selIn) {\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            });\n        }\n\n        if (that.prompt_box) {\n            that.prompt_box.close();\n        }\n        that.prompt_box = dialog;\n\n        var first = null;\n        var timeout = null;\n        var selected = null;\n\n        var name_element = dialog.querySelector(\".name\");\n        name_element.innerText = title;\n        var value_element = dialog.querySelector(\".value\");\n        value_element.value = value;\n\n        var input = value_element;\n        input.addEventListener(\"keydown\", function(e) {\n            dialog.is_modified = true;\n            if (e.keyCode == 27) {\n                //ESC\n                dialog.close();\n            } else if (e.keyCode == 13 && e.target.localName != \"textarea\") {\n                if (callback) {\n                    callback(this.value);\n                }\n                dialog.close();\n            } else {\n                return;\n            }\n            e.preventDefault();\n            e.stopPropagation();\n        });\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", function(e) {\n            if (callback) {\n                callback(input.value);\n            }\n            that.setDirty(true);\n            dialog.close();\n        });\n\n        var rect = canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n\n        setTimeout(function() {\n            input.focus();\n        }, 10);\n\n        return dialog;\n    };\n\n    LGraphCanvas.search_limit = -1;\n    LGraphCanvas.prototype.showSearchBox = function(event, options) {\n        // proposed defaults\n        var def_options = { slot_from: null\n                        ,node_from: null\n                        ,node_to: null\n                        ,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out\n                        ,type_filter_in: false                          // these are default: pass to set initially set values\n                        ,type_filter_out: false\n                        ,show_general_if_none_on_typefilter: true\n                        ,show_general_after_typefiltered: true\n                        ,hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave\n                        ,show_all_if_empty: true\n                        ,show_all_on_open: LiteGraph.search_show_all_on_open\n                    };\n        options = Object.assign(def_options, options || {});\n        \n\t\t//console.log(options);\n\t\t\n        var that = this;\n        var input_html = \"\";\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n        var root_document = canvas.ownerDocument || document;\n\n        var dialog = document.createElement(\"div\");\n        dialog.className = \"litegraph litesearchbox graphdialog rounded\";\n        dialog.innerHTML = \"<span class='name'>Search</span> <input autofocus type='text' class='value rounded'/>\";\n        if (options.do_type_filter){\n            dialog.innerHTML += \"<select class='slot_in_type_filter'><option value=''></option></select>\";\n            dialog.innerHTML += \"<select class='slot_out_type_filter'><option value=''></option></select>\";\n        }\n        dialog.innerHTML += \"<div class='helper'></div>\";\n        \n        if( root_document.fullscreenElement )\n\t        root_document.fullscreenElement.appendChild(dialog);\n\t\telse\n\t\t{\n\t\t    root_document.body.appendChild(dialog);\n\t\t\troot_document.body.style.overflow = \"hidden\";\n\t\t}\n        // dialog element has been appended\n        \n        if (options.do_type_filter){\n            var selIn = dialog.querySelector(\".slot_in_type_filter\");\n            var selOut = dialog.querySelector(\".slot_out_type_filter\");\n        }\n        \n        dialog.close = function() {\n            that.search_box = null;\n\t\t\tthis.blur();\n            canvas.focus();\n\t\t\troot_document.body.style.overflow = \"\";\n\n            setTimeout(function() {\n                that.canvas.focus();\n            }, 20); //important, if canvas loses focus keys wont be captured\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n\n        if (this.ds.scale > 1) {\n            dialog.style.transform = \"scale(\" + this.ds.scale + \")\";\n        }\n\n        // hide on mouse leave\n        if(options.hide_on_mouse_leave){\n            var prevent_timeout = false;\n            var timeout_close = null;\n            LiteGraph.pointerListenerAdd(dialog,\"enter\", function(e) {\n                if (timeout_close) {\n                    clearTimeout(timeout_close);\n                    timeout_close = null;\n                }\n            });\n            LiteGraph.pointerListenerAdd(dialog,\"leave\", function(e) {\n                if (prevent_timeout){\n                    return;\n                }\n                timeout_close = setTimeout(function() {\n                    dialog.close();\n                }, 500);\n            });\n            // if filtering, check focus changed to comboboxes and prevent closing\n            if (options.do_type_filter){\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n                selOut.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selOut.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selOut.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            }\n        }\n\n        if (that.search_box) {\n            that.search_box.close();\n        }\n        that.search_box = dialog;\n\n        var helper = dialog.querySelector(\".helper\");\n\n        var first = null;\n        var timeout = null;\n        var selected = null;\n\n        var input = dialog.querySelector(\"input\");\n        if (input) {\n            input.addEventListener(\"blur\", function(e) {\n                if(that.search_box)\n                    this.focus();\n            });\n            input.addEventListener(\"keydown\", function(e) {\n                if (e.keyCode == 38) {\n                    //UP\n                    changeSelection(false);\n                } else if (e.keyCode == 40) {\n                    //DOWN\n                    changeSelection(true);\n                } else if (e.keyCode == 27) {\n                    //ESC\n                    dialog.close();\n                } else if (e.keyCode == 13) {\n                    refreshHelper();\n                    if (selected) {\n                        select(selected.innerHTML);\n                    } else if (first) {\n                        select(first);\n                    } else {\n                        dialog.close();\n                    }\n                } else {\n                    if (timeout) {\n                        clearInterval(timeout);\n                    }\n                    timeout = setTimeout(refreshHelper, 250);\n                    return;\n                }\n                e.preventDefault();\n                e.stopPropagation();\n\t\t\t\te.stopImmediatePropagation();\n\t\t\t\treturn true;\n            });\n        }\n        \n        // if should filter on type, load and fill selected and choose elements if passed\n        if (options.do_type_filter){\n            if (selIn){\n                var aSlots = LiteGraph.slot_types_in;\n                var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length;\n                \n                if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION)\n                    options.type_filter_in = \"_event_\";\n                /* this will filter on * .. but better do it manually in case\n                else if(options.type_filter_in === \"\" || options.type_filter_in === 0)\n                    options.type_filter_in = \"*\";*/\n                \n                for (var iK=0; iK<nSlots; iK++){\n                    var opt = document.createElement('option');\n                    opt.value = aSlots[iK];\n                    opt.innerHTML = aSlots[iK];\n                    selIn.appendChild(opt);\n                    if(options.type_filter_in !==false && (options.type_filter_in+\"\").toLowerCase() == (aSlots[iK]+\"\").toLowerCase()){\n                        //selIn.selectedIndex ..\n                        opt.selected = true;\n\t\t\t\t\t\t//console.log(\"comparing IN \"+options.type_filter_in+\" :: \"+aSlots[iK]);\n\t                }else{\n\t\t\t\t\t\t//console.log(\"comparing OUT \"+options.type_filter_in+\" :: \"+aSlots[iK]);\n\t\t\t\t\t}\n\t\t\t\t}\n                selIn.addEventListener(\"change\",function(){\n                    refreshHelper();\n                });\n            }\n            if (selOut){\n                var aSlots = LiteGraph.slot_types_out;\n                var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; \n                \n                if (options.type_filter_out == LiteGraph.EVENT || options.type_filter_out == LiteGraph.ACTION)\n                    options.type_filter_out = \"_event_\";\n                /* this will filter on * .. but better do it manually in case\n                else if(options.type_filter_out === \"\" || options.type_filter_out === 0)\n                    options.type_filter_out = \"*\";*/\n                \n                for (var iK=0; iK<nSlots; iK++){\n                    var opt = document.createElement('option');\n                    opt.value = aSlots[iK];\n                    opt.innerHTML = aSlots[iK];\n                    selOut.appendChild(opt);\n                    if(options.type_filter_out !==false && (options.type_filter_out+\"\").toLowerCase() == (aSlots[iK]+\"\").toLowerCase()){\n                        //selOut.selectedIndex ..\n                        opt.selected = true;\n                    }\n                }\n                selOut.addEventListener(\"change\",function(){\n                    refreshHelper();\n                });\n            }\n        }\n        \n        //compute best position\n        var rect = canvas.getBoundingClientRect();\n\n        var left = ( event ? event.clientX : (rect.left + rect.width * 0.5) ) - 80;\n        var top = ( event ? event.clientY : (rect.top + rect.height * 0.5) ) - 20;\n        dialog.style.left = left + \"px\";\n        dialog.style.top = top + \"px\";\n\n\t\t//To avoid out of screen problems\n\t\tif(event.layerY > (rect.height - 200)) \n            helper.style.maxHeight = (rect.height - event.layerY - 20) + \"px\";\n\n\t\t/*\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n        canvas.parentNode.appendChild(dialog);\n\t\t*/\n\n        input.focus();\n        if (options.show_all_on_open) refreshHelper();\n\n        function select(name) {\n            if (name) {\n                if (that.onSearchBoxSelection) {\n                    that.onSearchBoxSelection(name, event, graphcanvas);\n                } else {\n                    var extra = LiteGraph.searchbox_extras[name.toLowerCase()];\n                    if (extra) {\n                        name = extra.type;\n                    }\n\n\t\t\t\t\tgraphcanvas.graph.beforeChange();\n                    var node = LiteGraph.createNode(name);\n                    if (node) {\n                        node.pos = graphcanvas.convertEventToCanvasOffset(\n                            event\n                        );\n                        graphcanvas.graph.add(node, false);\n                    }\n\n                    if (extra && extra.data) {\n                        if (extra.data.properties) {\n                            for (var i in extra.data.properties) {\n                                node.addProperty( i, extra.data.properties[i] );\n                            }\n                        }\n                        if (extra.data.inputs) {\n                            node.inputs = [];\n                            for (var i in extra.data.inputs) {\n                                node.addOutput(\n                                    extra.data.inputs[i][0],\n                                    extra.data.inputs[i][1]\n                                );\n                            }\n                        }\n                        if (extra.data.outputs) {\n                            node.outputs = [];\n                            for (var i in extra.data.outputs) {\n                                node.addOutput(\n                                    extra.data.outputs[i][0],\n                                    extra.data.outputs[i][1]\n                                );\n                            }\n                        }\n                        if (extra.data.title) {\n                            node.title = extra.data.title;\n                        }\n                        if (extra.data.json) {\n                            node.configure(extra.data.json);\n                        }\n\n                    }\n\n                    // join node after inserting\n                    if (options.node_from){\n                        var iS = false;\n                        switch (typeof options.slot_from){\n                            case \"string\":\n                                iS = options.node_from.findOutputSlot(options.slot_from);    \n                            break;\n                            case \"object\":\n                                if (options.slot_from.name){\n                                    iS = options.node_from.findOutputSlot(options.slot_from.name);\n                                }else{\n                                    iS = -1;\n                                }\n                                if (iS==-1 && typeof options.slot_from.slot_index !== \"undefined\") iS = options.slot_from.slot_index;\n                            break;\n                            case \"number\":\n                                iS = options.slot_from;\n                            break;\n                            default:\n                                iS = 0; // try with first if no name set\n                        }\n                        if (typeof options.node_from.outputs[iS] !== \"undefined\"){\n                            if (iS!==false && iS>-1){\n                                options.node_from.connectByType( iS, node, options.node_from.outputs[iS].type );\n                            }\n                        }else{\n                            // console.warn(\"cant find slot \" + options.slot_from);\n                        }\n                    }\n                    if (options.node_to){\n                        var iS = false;\n                        switch (typeof options.slot_from){\n                            case \"string\":\n                                iS = options.node_to.findInputSlot(options.slot_from);    \n                            break;\n                            case \"object\":\n                                if (options.slot_from.name){\n                                    iS = options.node_to.findInputSlot(options.slot_from.name);\n                                }else{\n                                    iS = -1;\n                                }\n                                if (iS==-1 && typeof options.slot_from.slot_index !== \"undefined\") iS = options.slot_from.slot_index;\n                            break;\n                            case \"number\":\n                                iS = options.slot_from;\n                            break;\n                            default:\n                                iS = 0; // try with first if no name set\n                        }\n                        if (typeof options.node_to.inputs[iS] !== \"undefined\"){\n                            if (iS!==false && iS>-1){\n                                // try connection\n                                options.node_to.connectByTypeOutput(iS,node,options.node_to.inputs[iS].type);\n                            }\n                        }else{\n                            // console.warn(\"cant find slot_nodeTO \" + options.slot_from);\n                        }\n                    }\n                    \n                    graphcanvas.graph.afterChange();\n                }\n            }\n\n            dialog.close();\n        }\n\n        function changeSelection(forward) {\n            var prev = selected;\n            if (selected) {\n                selected.classList.remove(\"selected\");\n            }\n            if (!selected) {\n                selected = forward\n                    ? helper.childNodes[0]\n                    : helper.childNodes[helper.childNodes.length];\n            } else {\n                selected = forward\n                    ? selected.nextSibling\n                    : selected.previousSibling;\n                if (!selected) {\n                    selected = prev;\n                }\n            }\n            if (!selected) {\n                return;\n            }\n            selected.classList.add(\"selected\");\n            selected.scrollIntoView({block: \"end\", behavior: \"smooth\"});\n        }\n\n        function refreshHelper() {\n            timeout = null;\n            var str = input.value;\n            first = null;\n            helper.innerHTML = \"\";\n            if (!str && !options.show_all_if_empty) {\n                return;\n            }\n\n            if (that.onSearchBox) {\n                var list = that.onSearchBox(helper, str, graphcanvas);\n                if (list) {\n                    for (var i = 0; i < list.length; ++i) {\n                        addResult(list[i]);\n                    }\n                }\n            } else {\n                var c = 0;\n                str = str.toLowerCase();\n\t\t\t\tvar filter = graphcanvas.filter || graphcanvas.graph.filter;\n\n                // filter by type preprocess\n                if(options.do_type_filter && that.search_box){\n                    var sIn = that.search_box.querySelector(\".slot_in_type_filter\");\n                    var sOut = that.search_box.querySelector(\".slot_out_type_filter\");\n                }else{\n                    var sIn = false;\n                    var sOut = false;\n                }\n                \n                //extras\n                for (var i in LiteGraph.searchbox_extras) {\n                    var extra = LiteGraph.searchbox_extras[i];\n                    if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) {\n                        continue;\n                    }\n\t\t\t\t\tvar ctor = LiteGraph.registered_node_types[ extra.type ];\n\t\t\t\t\tif( ctor && ctor.filter != filter )\n\t\t\t\t\t\tcontinue;\n                    if( ! inner_test_filter(extra.type) )\n                        continue;\n                    addResult( extra.desc, \"searchbox_extra\" );\n                    if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                        break;\n                    }\n                }\n\n\t\t\t\tvar filtered = null;\n                if (Array.prototype.filter) { //filter supported\n                    var keys = Object.keys( LiteGraph.registered_node_types ); //types\n                    var filtered = keys.filter( inner_test_filter );\n                } else {\n\t\t\t\t\tfiltered = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i) )\n\t\t\t\t\t\t\tfiltered.push(i);\n                    }\n                }\n\n\t\t\t\tfor (var i = 0; i < filtered.length; i++) {\n\t\t\t\t\taddResult(filtered[i]);\n\t\t\t\t\tif ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n                \n                // add general type if filtering\n                if (options.show_general_after_typefiltered\n                    && (sIn.value || sOut.value) \n                ){\n                    filtered_extra = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?\"*\":false, outTypeOverride: sOut&&sOut.value?\"*\":false}) )\n\t\t\t\t\t\t\tfiltered_extra.push(i);\n                    }\n                    for (var i = 0; i < filtered_extra.length; i++) {\n                        addResult(filtered_extra[i], \"generic_type\");\n                        if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                            break;\n                        }\n                    }\n                }\n                \n                // check il filtering gave no results\n                if ((sIn.value || sOut.value) && \n                    ( (helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) )\n                ){\n                    filtered_extra = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i, {skipFilter: true}) )\n\t\t\t\t\t\t\tfiltered_extra.push(i);\n                    }\n                    for (var i = 0; i < filtered_extra.length; i++) {\n                        addResult(filtered_extra[i], \"not_in_filter\");\n                        if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                            break;\n                        }\n                    }\n                }\n                \n\t\t\t\tfunction inner_test_filter( type, optsIn )\n\t\t\t\t{\n                    var optsIn = optsIn || {};\n                    var optsDef = { skipFilter: false\n                                    ,inTypeOverride: false\n                                    ,outTypeOverride: false\n                                  };\n                    var opts = Object.assign(optsDef,optsIn);\n\t\t\t\t\tvar ctor = LiteGraph.registered_node_types[ type ];\n\t\t\t\t\tif(filter && ctor.filter != filter )\n\t\t\t\t\t\treturn false;\n                    if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1)\n                        return false;\n                    \n                    // filter by slot IN, OUT types\n                    if(options.do_type_filter && !opts.skipFilter){\n                        var sType = type;\n                        \n                        var sV = sIn.value;\n                        if (opts.inTypeOverride!==false) sV = opts.inTypeOverride;\n\t\t\t\t\t\t//if (sV.toLowerCase() == \"_event_\") sV = LiteGraph.EVENT; // -1\n                        \n                        if(sIn && sV){\n                            //console.log(\"will check filter against \"+sV);\n                            if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored\n                                //console.debug(\"check \"+sType+\" in \"+LiteGraph.registered_slot_in_types[sV].nodes);\n                                var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType);\n                                if (doesInc!==false){\n                                    //console.log(sType+\" HAS \"+sV);\n                                }else{\n                                    /*console.debug(LiteGraph.registered_slot_in_types[sV]);\n                                    console.log(+\" DONT includes \"+type);*/\n                                    return false;\n                                }\n                            }\n                        }\n                        \n                        var sV = sOut.value;\n                        if (opts.outTypeOverride!==false) sV = opts.outTypeOverride;\n                        //if (sV.toLowerCase() == \"_event_\") sV = LiteGraph.EVENT; // -1\n                        \n                        if(sOut && sV){\n                            //console.log(\"search will check filter against \"+sV);\n                            if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored\n                                //console.debug(\"check \"+sType+\" in \"+LiteGraph.registered_slot_out_types[sV].nodes);\n                                var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType);\n                                if (doesInc!==false){\n                                    //console.log(sType+\" HAS \"+sV);\n                                }else{\n                                    /*console.debug(LiteGraph.registered_slot_out_types[sV]);\n                                    console.log(+\" DONT includes \"+type);*/\n                                    return false;\n                                }\n                            }\n                        }\n                    }\n                    return true;\n\t\t\t\t}\n            }\n\n            function addResult(type, className) {\n                var help = document.createElement(\"div\");\n                if (!first) {\n                    first = type;\n                }\n                help.innerText = type;\n                help.dataset[\"type\"] = escape(type);\n                help.className = \"litegraph lite-search-item\";\n                if (className) {\n                    help.className += \" \" + className;\n                }\n                help.addEventListener(\"click\", function(e) {\n                    select(unescape(this.dataset[\"type\"]));\n                });\n                helper.appendChild(help);\n            }\n        }\n\n        return dialog;\n    };\n\n    LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) {\n        if (!node || node.properties[property] === undefined) {\n            return;\n        }\n\n        options = options || {};\n        var that = this;\n\n        var info = node.getPropertyInfo(property);\n\t\tvar type = info.type;\n\n        var input_html = \"\";\n\n        if (type == \"string\" || type == \"number\" || type == \"array\" || type == \"object\") {\n            input_html = \"<input autofocus type='text' class='value'/>\";\n        } else if ( (type == \"enum\" || type == \"combo\") && info.values) {\n            input_html = \"<select autofocus type='text' class='value'>\";\n            for (var i in info.values) {\n                var v = i;\n\t\t\t\tif( info.values.constructor === Array )\n\t\t\t\t\tv = info.values[i];\n\n                input_html +=\n                    \"<option value='\" +\n                    v +\n                    \"' \" +\n                    (v == node.properties[property] ? \"selected\" : \"\") +\n                    \">\" +\n                    info.values[i] +\n                    \"</option>\";\n            }\n            input_html += \"</select>\";\n        } else if (type == \"boolean\" || type == \"toggle\") {\n            input_html =\n                \"<input autofocus type='checkbox' class='value' \" +\n                (node.properties[property] ? \"checked\" : \"\") +\n                \"/>\";\n        } else {\n            console.warn(\"unknown type: \" + type);\n            return;\n        }\n\n        var dialog = this.createDialog(\n            \"<span class='name'>\" +\n                (info.label ? info.label : property) +\n                \"</span>\" +\n                input_html +\n                \"<button>OK</button>\",\n            options\n        );\n\n        var input = false;\n        if ((type == \"enum\" || type == \"combo\") && info.values) {\n            input = dialog.querySelector(\"select\");\n            input.addEventListener(\"change\", function(e) {\n                dialog.modified();\n                setValue(e.target.value);\n                //var index = e.target.value;\n                //setValue( e.options[e.selectedIndex].value );\n            });\n        } else if (type == \"boolean\" || type == \"toggle\") {\n            input = dialog.querySelector(\"input\");\n            if (input) {\n                input.addEventListener(\"click\", function(e) {\n                    dialog.modified();\n                    setValue(!!input.checked);\n                });\n            }\n        } else {\n            input = dialog.querySelector(\"input\");\n            if (input) {\n                input.addEventListener(\"blur\", function(e) {\n                    this.focus();\n                });\n\n\t\t\t\tvar v = node.properties[property] !== undefined ? node.properties[property] : \"\";\n\t\t\t\tif (type !== 'string') {\n                    v = JSON.stringify(v);\n                }\n\n                input.value = v;\n                input.addEventListener(\"keydown\", function(e) {\n                    if (e.keyCode == 27) {\n                        //ESC\n                        dialog.close();\n                    } else if (e.keyCode == 13) {\n                        // ENTER\n                        inner(); // save\n                    } else if (e.keyCode != 13) {\n                        dialog.modified();\n                        return;\n                    }\n                    e.preventDefault();\n                    e.stopPropagation();\n                });\n            }\n        }\n        if (input) input.focus();\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", inner);\n\n        function inner() {\n            setValue(input.value);\n        }\n\n        function setValue(value) {\n\n\t\t\tif(info && info.values && info.values.constructor === Object && info.values[value] != undefined )\n\t\t\t\tvalue = info.values[value];\n\n            if (typeof node.properties[property] == \"number\") {\n                value = Number(value);\n            }\n            if (type == \"array\" || type == \"object\") {\n                value = JSON.parse(value);\n            }\n            node.properties[property] = value;\n            if (node.graph) {\n                node.graph._version++;\n            }\n            if (node.onPropertyChanged) {\n                node.onPropertyChanged(property, value);\n            }\n\t\t\tif(options.onclose)\n\t\t\t\toptions.onclose();\n            dialog.close();\n            node.setDirtyCanvas(true, true);\n        }\n\n\t\treturn dialog;\n    };\n\n    // TODO refactor, theer are different dialog, some uses createDialog, some dont\n    LGraphCanvas.prototype.createDialog = function(html, options) {\n        var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true };\n        options = Object.assign(def_options, options || {});\n\n        var dialog = document.createElement(\"div\");\n        dialog.className = \"graphdialog\";\n        dialog.innerHTML = html;\n        dialog.is_modified = false;\n\n        var rect = this.canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (options.position) {\n            offsetx += options.position[0];\n            offsety += options.position[1];\n        } else if (options.event) {\n            offsetx += options.event.clientX;\n            offsety += options.event.clientY;\n        } //centered\n        else {\n            offsetx += this.canvas.width * 0.5;\n            offsety += this.canvas.height * 0.5;\n        }\n\n        dialog.style.left = offsetx + \"px\";\n        dialog.style.top = offsety + \"px\";\n\n        this.canvas.parentNode.appendChild(dialog);\n        \n        // acheck for input and use default behaviour: save on enter, close on esc\n        if (options.checkForInput){\n            var aI = [];\n            var focused = false;\n            if (aI = dialog.querySelectorAll(\"input\")){\n                aI.forEach(function(iX) {\n                    iX.addEventListener(\"keydown\",function(e){\n                        dialog.modified();\n                        if (e.keyCode == 27) {\n                            dialog.close();\n                        } else if (e.keyCode != 13) {\n                            return;\n                        }\n                        // set value ?\n                        e.preventDefault();\n                        e.stopPropagation();\n                    });\n                    if (!focused) iX.focus();\n                });\n            }\n        }\n        \n        dialog.modified = function(){\n            dialog.is_modified = true;\n        }\n        dialog.close = function() {\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n        \n        var dialogCloseTimer = null;\n        var prevent_timeout = false;\n        dialog.addEventListener(\"mouseleave\", function(e) {\n            if (prevent_timeout)\n                return;\n            if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        dialog.addEventListener(\"mouseenter\", function(e) {\n            if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        var selInDia = dialog.querySelectorAll(\"select\");\n        if (selInDia){\n            // if filtering, check focus changed to comboboxes and prevent closing\n            selInDia.forEach(function(selIn) {\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            });\n        }\n\n        return dialog;\n    };\n\n\tLGraphCanvas.prototype.createPanel = function(title, options) {\n\t\toptions = options || {};\n\n\t\tvar ref_window = options.window || window;\n\t\tvar root = document.createElement(\"div\");\n\t\troot.className = \"litegraph dialog\";\n\t\troot.innerHTML = \"<div class='dialog-header'><span class='dialog-title'></span></div><div class='dialog-content'></div><div style='display:none;' class='dialog-alt-content'></div><div class='dialog-footer'></div>\";\n\t\troot.header = root.querySelector(\".dialog-header\");\n\n\t\tif(options.width)\n\t\t\troot.style.width = options.width + (options.width.constructor === Number ? \"px\" : \"\");\n\t\tif(options.height)\n\t\t\troot.style.height = options.height + (options.height.constructor === Number ? \"px\" : \"\");\n\t\tif(options.closable)\n\t\t{\n\t\t\tvar close = document.createElement(\"span\");\n\t\t\tclose.innerHTML = \"&#10005;\";\n\t\t\tclose.classList.add(\"close\");\n\t\t\tclose.addEventListener(\"click\",function(){\n\t\t\t\troot.close();\n\t\t\t});\n\t\t\troot.header.appendChild(close);\n\t\t}\n\t\troot.title_element = root.querySelector(\".dialog-title\");\n\t\troot.title_element.innerText = title;\n\t\troot.content = root.querySelector(\".dialog-content\");\n        root.alt_content = root.querySelector(\".dialog-alt-content\");\n\t\troot.footer = root.querySelector(\".dialog-footer\");\n\n\t\troot.close = function()\n\t\t{\n\t\t    if (root.onClose && typeof root.onClose == \"function\"){\n\t\t        root.onClose();\n\t\t    }\n            if(root.parentNode)\n\t\t        root.parentNode.removeChild(root);\n\t\t    /* XXX CHECK THIS */\n\t\t    if(this.parentNode){\n\t\t    \tthis.parentNode.removeChild(this);\n\t\t    }\n\t\t    /* XXX this was not working, was fixed with an IF, check this */\n\t\t}\n\n        // function to swap panel content\n        root.toggleAltContent = function(force){\n            if (typeof force != \"undefined\"){\n                var vTo = force ? \"block\" : \"none\";\n                var vAlt = force ? \"none\" : \"block\";\n            }else{\n                var vTo = root.alt_content.style.display != \"block\" ? \"block\" : \"none\";\n                var vAlt = root.alt_content.style.display != \"block\" ? \"none\" : \"block\";\n            }\n            root.alt_content.style.display = vTo;\n            root.content.style.display = vAlt;\n        }\n        \n        root.toggleFooterVisibility = function(force){\n            if (typeof force != \"undefined\"){\n                var vTo = force ? \"block\" : \"none\";\n            }else{\n                var vTo = root.footer.style.display != \"block\" ? \"block\" : \"none\";\n            }\n            root.footer.style.display = vTo;\n        }\n        \n\t\troot.clear = function()\n\t\t{\n\t\t\tthis.content.innerHTML = \"\";\n\t\t}\n\n\t\troot.addHTML = function(code, classname, on_footer)\n\t\t{\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\tif(classname)\n\t\t\t\telem.className = classname;\n\t\t\telem.innerHTML = code;\n\t\t\tif(on_footer)\n\t\t\t\troot.footer.appendChild(elem);\n\t\t\telse\n\t\t\t\troot.content.appendChild(elem);\n\t\t\treturn elem;\n\t\t}\n\n\t\troot.addButton = function( name, callback, options )\n\t\t{\n\t\t\tvar elem = document.createElement(\"button\");\n\t\t\telem.innerText = name;\n\t\t\telem.options = options;\n\t\t\telem.classList.add(\"btn\");\n\t\t\telem.addEventListener(\"click\",callback);\n\t\t\troot.footer.appendChild(elem);\n\t\t\treturn elem;\n\t\t}\n\n\t\troot.addSeparator = function()\n\t\t{\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\telem.className = \"separator\";\n\t\t\troot.content.appendChild(elem);\n\t\t}\n\n\t\troot.addWidget = function( type, name, value, options, callback )\n\t\t{\n\t\t\toptions = options || {};\n\t\t\tvar str_value = String(value);\n\t\t\ttype = type.toLowerCase();\n\t\t\tif(type == \"number\")\n\t\t\t\tstr_value = value.toFixed(3);\n\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\telem.className = \"property\";\n\t\t\telem.innerHTML = \"<span class='property_name'></span><span class='property_value'></span>\";\n\t\t\telem.querySelector(\".property_name\").innerText = options.label || name;\n\t\t\tvar value_element = elem.querySelector(\".property_value\");\n\t\t\tvalue_element.innerText = str_value;\n\t\t\telem.dataset[\"property\"] = name;\n\t\t\telem.dataset[\"type\"] = options.type || type;\n\t\t\telem.options = options;\n\t\t\telem.value = value;\n\n\t\t\tif( type == \"code\" )\n\t\t\t\telem.addEventListener(\"click\", function(e){ root.inner_showCodePad( this.dataset[\"property\"] ); });\n\t\t\telse if (type == \"boolean\")\n\t\t\t{\n\t\t\t\telem.classList.add(\"boolean\");\n\t\t\t\tif(value)\n\t\t\t\t\telem.classList.add(\"bool-on\");\n\t\t\t\telem.addEventListener(\"click\", function(){ \n\t\t\t\t\t//var v = node.properties[this.dataset[\"property\"]]; \n\t\t\t\t\t//node.setProperty(this.dataset[\"property\"],!v); this.innerText = v ? \"true\" : \"false\"; \n\t\t\t\t\tvar propname = this.dataset[\"property\"];\n\t\t\t\t\tthis.value = !this.value;\n\t\t\t\t\tthis.classList.toggle(\"bool-on\");\n\t\t\t\t\tthis.querySelector(\".property_value\").innerText = this.value ? \"true\" : \"false\";\n\t\t\t\t\tinnerChange(propname, this.value );\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if (type == \"string\" || type == \"number\")\n\t\t\t{\n\t\t\t\tvalue_element.setAttribute(\"contenteditable\",true);\n\t\t\t\tvalue_element.addEventListener(\"keydown\", function(e){ \n\t\t\t\t\tif(e.code == \"Enter\" && (type != \"string\" || !e.shiftKey)) // allow for multiline\n\t\t\t\t\t{\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tthis.blur();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tvalue_element.addEventListener(\"blur\", function(){ \n\t\t\t\t\tvar v = this.innerText;\n\t\t\t\t\tvar propname = this.parentNode.dataset[\"property\"];\n\t\t\t\t\tvar proptype = this.parentNode.dataset[\"type\"];\n\t\t\t\t\tif( proptype == \"number\")\n\t\t\t\t\t\tv = Number(v);\n\t\t\t\t\tinnerChange(propname, v);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if (type == \"enum\" || type == \"combo\") {\n\t\t\t\tvar str_value = LGraphCanvas.getPropertyPrintableValue( value, options.values );\n\t\t\t\tvalue_element.innerText = str_value;\n\n\t\t\t\tvalue_element.addEventListener(\"click\", function(event){ \n\t\t\t\t\tvar values = options.values || [];\n\t\t\t\t\tvar propname = this.parentNode.dataset[\"property\"];\n\t\t\t\t\tvar elem_that = this;\n\t\t\t\t\tvar menu = new LiteGraph.ContextMenu(values,{\n\t\t\t\t\t\t\tevent: event,\n\t\t\t\t\t\t\tclassName: \"dark\",\n\t\t\t\t\t\t\tcallback: inner_clicked\n\t\t\t\t\t\t},\n\t\t\t\t\t\tref_window);\n\t\t\t\t\tfunction inner_clicked(v, option, event) {\n\t\t\t\t\t\t//node.setProperty(propname,v); \n\t\t\t\t\t\t//graphcanvas.dirty_canvas = true;\n\t\t\t\t\t\telem_that.innerText = v;\n\t\t\t\t\t\tinnerChange(propname,v);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t});\n            }\n\n\t\t\troot.content.appendChild(elem);\n\n\t\t\tfunction innerChange(name, value)\n\t\t\t{\n\t\t\t\t//console.log(\"change\",name,value);\n\t\t\t\t//that.dirty_canvas = true;\n\t\t\t\tif(options.callback)\n\t\t\t\t\toptions.callback(name,value,options);\n\t\t\t\tif(callback)\n\t\t\t\t\tcallback(name,value,options);\n\t\t\t}\n\n\t\t\treturn elem;\n\t\t}\n\n        if (root.onOpen && typeof root.onOpen == \"function\") root.onOpen();\n        \n\t\treturn root;\n\t};\n\n\tLGraphCanvas.getPropertyPrintableValue = function(value, values)\n\t{\n\t\tif(!values)\n\t\t\treturn String(value);\n\n\t\tif(values.constructor === Array)\n\t\t{\n\t\t\treturn String(value);\t\t\t\n\t\t}\n\n\t\tif(values.constructor === Object)\n\t\t{\n\t\t\tvar desc_value = \"\";\n\t\t\tfor(var k in values)\n\t\t\t{\n\t\t\t\tif(values[k] != value)\n\t\t\t\t\tcontinue;\n\t\t\t\tdesc_value = k;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn String(value) + \" (\"+desc_value+\")\";\n\t\t}\n\t}\n\n    LGraphCanvas.prototype.closePanels = function(){\n        var panel = document.querySelector(\"#node-panel\");\n\t\tif(panel)\n\t\t\tpanel.close();\n        var panel = document.querySelector(\"#option-panel\");\n\t\tif(panel)\n\t\t\tpanel.close();\n    }\n    \n    LGraphCanvas.prototype.showShowGraphOptionsPanel = function(refOpts, obEv, refMenu, refMenu2){\n        if(this.constructor && this.constructor.name == \"HTMLDivElement\"){\n            // assume coming from the menu event click\n            if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){\n                console.warn(\"Canvas not found\"); // need a ref to canvas obj\n                /*console.debug(event);\n                console.debug(event.target);*/\n                return;\n            }\n            var graphcanvas = obEv.event.target.lgraphcanvas;\n        }else{\n            // assume called internally\n            var graphcanvas = this;\n        }\n        graphcanvas.closePanels();\n        var ref_window = graphcanvas.getCanvasWindow();\n        panel = graphcanvas.createPanel(\"Options\",{\n                                            closable: true\n                                            ,window: ref_window\n                                            ,onOpen: function(){\n                                                graphcanvas.OPTIONPANEL_IS_OPEN = true;\n                                            }\n                                            ,onClose: function(){\n                                                graphcanvas.OPTIONPANEL_IS_OPEN = false;\n                                                graphcanvas.options_panel = null;\n                                            }\n                                        });\n        graphcanvas.options_panel = panel;\n        panel.id = \"option-panel\";\n\t\tpanel.classList.add(\"settings\");\n        \n        function inner_refresh(){\n            \n            panel.content.innerHTML = \"\"; //clear\n\n            var fUpdate = function(name, value, options){\n                switch(name){\n                    /*case \"Render mode\":\n                        // Case \"\".. \n                        if (options.values && options.key){\n                            var kV = Object.values(options.values).indexOf(value);\n                            if (kV>=0 && options.values[kV]){\n                                console.debug(\"update graph options: \"+options.key+\": \"+kV);\n                                graphcanvas[options.key] = kV;\n                                //console.debug(graphcanvas);\n                                break;\n                            }\n                        }\n                        console.warn(\"unexpected options\");\n                        console.debug(options);\n                        break;*/\n                    default:\n                        //console.debug(\"want to update graph options: \"+name+\": \"+value);\n                        if (options && options.key){\n                            name = options.key;\n                        }\n                        if (options.values){\n                            value = Object.values(options.values).indexOf(value);\n                        }\n                        //console.debug(\"update graph option: \"+name+\": \"+value);\n                        graphcanvas[name] = value;\n                        break;\n                }\n            };\n            \n            // panel.addWidget( \"string\", \"Graph name\", \"\", {}, fUpdate); // implement\n            \n            var aProps = LiteGraph.availableCanvasOptions;\n            aProps.sort();\n            for(var pI in aProps){\n                var pX = aProps[pI];\n                panel.addWidget( \"boolean\", pX, graphcanvas[pX], {key: pX, on: \"True\", off: \"False\"}, fUpdate);\n            }\n            \n            var aLinks = [ graphcanvas.links_render_mode ];\n            panel.addWidget( \"combo\", \"Render mode\", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: \"links_render_mode\", values: LiteGraph.LINK_RENDER_MODES}, fUpdate);\n            \n            panel.addSeparator();\n            \n            panel.footer.innerHTML = \"\"; // clear\n\n\t\t}\n        inner_refresh();\n\n\t\tgraphcanvas.canvas.parentNode.appendChild( panel );\n    }\n    \n    LGraphCanvas.prototype.showShowNodePanel = function( node )\n\t{\n\t\tthis.SELECTED_NODE = node;\n\t\tthis.closePanels();\n\t\tvar ref_window = this.getCanvasWindow();\n        var that = this;\n\t\tvar graphcanvas = this;\n\t\tvar panel = this.createPanel(node.title || \"\",{\n                                                    closable: true\n                                                    ,window: ref_window\n                                                    ,onOpen: function(){\n                                                        graphcanvas.NODEPANEL_IS_OPEN = true;\n                                                    }\n                                                    ,onClose: function(){\n                                                        graphcanvas.NODEPANEL_IS_OPEN = false;\n                                                        graphcanvas.node_panel = null;\n                                                    }\n                                                });\n        graphcanvas.node_panel = panel;\n\t\tpanel.id = \"node-panel\";\n\t\tpanel.node = node;\n\t\tpanel.classList.add(\"settings\");\n\n\t\tfunction inner_refresh()\n\t\t{\n\t\t\tpanel.content.innerHTML = \"\"; //clear\n\t\t\tpanel.addHTML(\"<span class='node_type'>\"+node.type+\"</span><span class='node_desc'>\"+(node.constructor.desc || \"\")+\"</span><span class='separator'></span>\");\n\n\t\t\tpanel.addHTML(\"<h3>Properties</h3>\");\n\n            var fUpdate = function(name,value){\n                            graphcanvas.graph.beforeChange(node);\n                            switch(name){\n                                case \"Title\":\n                                    node.title = value;\n                                    break;\n                                case \"Mode\":\n                                    var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value);\n                                    if (kV>=0 && LiteGraph.NODE_MODES[kV]){\n                                        node.changeMode(kV);\n                                    }else{\n                                        console.warn(\"unexpected mode: \"+value);\n                                    }\n                                    break;\n                                case \"Color\":\n                                    if (LGraphCanvas.node_colors[value]){\n                                        node.color = LGraphCanvas.node_colors[value].color;\n                                        node.bgcolor = LGraphCanvas.node_colors[value].bgcolor;\n                                    }else{\n                                        console.warn(\"unexpected color: \"+value);\n                                    }\n                                    break;\n                                default:\n                                    node.setProperty(name,value);\n                                    break;\n                            }\n                            graphcanvas.graph.afterChange();\n                            graphcanvas.dirty_canvas = true;\n                        };\n            \n            panel.addWidget( \"string\", \"Title\", node.title, {}, fUpdate);\n            \n            panel.addWidget( \"combo\", \"Mode\", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate);\n            \n            var nodeCol = \"\";\n            if (node.color !== undefined){\n                nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; });\n            }\n            \n            panel.addWidget( \"combo\", \"Color\", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate);\n            \n            for(var pName in node.properties)\n\t\t\t{\n\t\t\t\tvar value = node.properties[pName];\n\t\t\t\tvar info = node.getPropertyInfo(pName);\n\t\t\t\tvar type = info.type || \"string\";\n\n\t\t\t\t//in case the user wants control over the side panel widget\n\t\t\t\tif( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tpanel.addWidget( info.widget || info.type, pName, value, info, fUpdate);\n\t\t\t}\n\n\t\t\tpanel.addSeparator();\n\n\t\t\tif(node.onShowCustomPanelInfo)\n\t\t\t\tnode.onShowCustomPanelInfo(panel);\n\n            panel.footer.innerHTML = \"\"; // clear\n\t\t\tpanel.addButton(\"Delete\",function(){\n\t\t\t\tif(node.block_delete)\n\t\t\t\t\treturn;\n\t\t\t\tnode.graph.remove(node);\n\t\t\t\tpanel.close();\n\t\t\t}).classList.add(\"delete\");\n\t\t}\n\n\t\tpanel.inner_showCodePad = function( propname )\n\t\t{\n            panel.classList.remove(\"settings\");\n            panel.classList.add(\"centered\");\n\n            \n\t\t\t/*if(window.CodeFlask) //disabled for now\n\t\t\t{\n\t\t\t\tpanel.content.innerHTML = \"<div class='code'></div>\";\n\t\t\t\tvar flask = new CodeFlask( \"div.code\", { language: 'js' });\n\t\t\t\tflask.updateCode(node.properties[propname]);\n\t\t\t\tflask.onUpdate( function(code) {\n\t\t\t\t\tnode.setProperty(propname, code);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse\n\t\t\t{*/\n\t\t\t\tpanel.alt_content.innerHTML = \"<textarea class='code'></textarea>\";\n\t\t\t\tvar textarea = panel.alt_content.querySelector(\"textarea\");\n                var fDoneWith = function(){\n                    panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = \"block\"; // panel.close();\n                    panel.toggleFooterVisibility(true);\n                    textarea.parentNode.removeChild(textarea);\n                    panel.classList.add(\"settings\");\n                    panel.classList.remove(\"centered\");\n                    inner_refresh();\n                }\n\t\t\t\ttextarea.value = node.properties[propname];\n\t\t\t\ttextarea.addEventListener(\"keydown\", function(e){\n\t\t\t\t\tif(e.code == \"Enter\" && e.ctrlKey )\n\t\t\t\t\t{\n\t\t\t\t\t\tnode.setProperty(propname, textarea.value);\n                        fDoneWith();\n\t\t\t\t\t}\n\t\t\t\t});\n                panel.toggleAltContent(true);\n                panel.toggleFooterVisibility(false);\n\t\t\t\ttextarea.style.height = \"calc(100% - 40px)\";\n\t\t\t/*}*/\n\t\t\tvar assign = panel.addButton( \"Assign\", function(){\n\t\t\t\tnode.setProperty(propname, textarea.value);\n                fDoneWith();\n\t\t\t});\n\t\t\tpanel.alt_content.appendChild(assign); //panel.content.appendChild(assign);\n\t\t\tvar button = panel.addButton( \"Close\", fDoneWith);\n\t\t\tbutton.style.float = \"right\";\n\t\t\tpanel.alt_content.appendChild(button); // panel.content.appendChild(button);\n\t\t}\n\n\t\tinner_refresh();\n\n\t\tthis.canvas.parentNode.appendChild( panel );\n\t}\n\t\n\tLGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node)\n\t{\n\t\tconsole.log(\"showing subgraph properties dialog\");\n\n\t\tvar old_panel = this.canvas.parentNode.querySelector(\".subgraph_dialog\");\n\t\tif(old_panel)\n\t\t\told_panel.close();\n\n\t\tvar panel = this.createPanel(\"Subgraph Inputs\",{closable:true, width: 500});\n\t\tpanel.node = node;\n\t\tpanel.classList.add(\"subgraph_dialog\");\n\n\t\tfunction inner_refresh()\n\t\t{\n\t\t\tpanel.clear();\n\n\t\t\t//show currents\n\t\t\tif(node.inputs)\n\t\t\t\tfor(var i = 0; i < node.inputs.length; ++i)\n\t\t\t\t{\n\t\t\t\t\tvar input = node.inputs[i];\n\t\t\t\t\tif(input.not_subgraph_input)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tvar html = \"<button>&#10005;</button> <span class='bullet_icon'></span><span class='name'></span><span class='type'></span>\";\n\t\t\t\t\tvar elem = panel.addHTML(html,\"subgraph_property\");\n\t\t\t\t\telem.dataset[\"name\"] = input.name;\n\t\t\t\t\telem.dataset[\"slot\"] = i;\n\t\t\t\t\telem.querySelector(\".name\").innerText = input.name;\n\t\t\t\t\telem.querySelector(\".type\").innerText = input.type;\n\t\t\t\t\telem.querySelector(\"button\").addEventListener(\"click\",function(e){\n\t\t\t\t\t\tnode.removeInput( Number( this.parentNode.dataset[\"slot\"] ) );\n\t\t\t\t\t\tinner_refresh();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\n\t\t//add extra\n\t\tvar html = \" + <span class='label'>Name</span><input class='name'/><span class='label'>Type</span><input class='type'></input><button>+</button>\";\n\t\tvar elem = panel.addHTML(html,\"subgraph_property extra\", true);\n\t\telem.querySelector(\"button\").addEventListener(\"click\", function(e){\n\t\t\tvar elem = this.parentNode;\n\t\t\tvar name = elem.querySelector(\".name\").value;\n\t\t\tvar type = elem.querySelector(\".type\").value;\n\t\t\tif(!name || node.findInputSlot(name) != -1)\n\t\t\t\treturn;\n\t\t\tnode.addInput(name,type);\n\t\t\telem.querySelector(\".name\").value = \"\";\n\t\t\telem.querySelector(\".type\").value = \"\";\n\t\t\tinner_refresh();\n\t\t});\n\n\t\tinner_refresh();\n\t    this.canvas.parentNode.appendChild(panel);\n\t\treturn panel;\n\t}\n    LGraphCanvas.prototype.showSubgraphPropertiesDialogRight = function (node) {\n\n        // console.log(\"showing subgraph properties dialog\");\n        var that = this;\n        // old_panel if old_panel is exist close it\n        var old_panel = this.canvas.parentNode.querySelector(\".subgraph_dialog\");\n        if (old_panel)\n            old_panel.close();\n        // new panel\n        var panel = this.createPanel(\"Subgraph Outputs\", { closable: true, width: 500 });\n        panel.node = node;\n        panel.classList.add(\"subgraph_dialog\");\n\n        function inner_refresh() {\n            panel.clear();\n            //show currents\n            if (node.outputs)\n                for (var i = 0; i < node.outputs.length; ++i) {\n                    var input = node.outputs[i];\n                    if (input.not_subgraph_output)\n                        continue;\n                    var html = \"<button>&#10005;</button> <span class='bullet_icon'></span><span class='name'></span><span class='type'></span>\";\n                    var elem = panel.addHTML(html, \"subgraph_property\");\n                    elem.dataset[\"name\"] = input.name;\n                    elem.dataset[\"slot\"] = i;\n                    elem.querySelector(\".name\").innerText = input.name;\n                    elem.querySelector(\".type\").innerText = input.type;\n                    elem.querySelector(\"button\").addEventListener(\"click\", function (e) {\n                        node.removeOutput(Number(this.parentNode.dataset[\"slot\"]));\n                        inner_refresh();\n                    });\n                }\n        }\n\n        //add extra\n        var html = \" + <span class='label'>Name</span><input class='name'/><span class='label'>Type</span><input class='type'></input><button>+</button>\";\n        var elem = panel.addHTML(html, \"subgraph_property extra\", true);\n        elem.querySelector(\".name\").addEventListener(\"keydown\", function (e) {\n            if (e.keyCode == 13) {\n                addOutput.apply(this)\n            }\n        })\n        elem.querySelector(\"button\").addEventListener(\"click\", function (e) {\n            addOutput.apply(this)\n        });\n        function addOutput() {\n            var elem = this.parentNode;\n            var name = elem.querySelector(\".name\").value;\n            var type = elem.querySelector(\".type\").value;\n            if (!name || node.findOutputSlot(name) != -1)\n                return;\n            node.addOutput(name, type);\n            elem.querySelector(\".name\").value = \"\";\n            elem.querySelector(\".type\").value = \"\";\n            inner_refresh();\n        }\n\n        inner_refresh();\n        this.canvas.parentNode.appendChild(panel);\n        return panel;\n    }\n\tLGraphCanvas.prototype.checkPanels = function()\n\t{\n\t\tif(!this.canvas)\n\t\t\treturn;\n\t\tvar panels = this.canvas.parentNode.querySelectorAll(\".litegraph.dialog\");\n\t\tfor(var i = 0; i < panels.length; ++i)\n\t\t{\n\t\t\tvar panel = panels[i];\n\t\t\tif( !panel.node )\n\t\t\t\tcontinue;\n\t\t\tif( !panel.node.graph || panel.graph != this.graph )\n\t\t\t\tpanel.close();\n\t\t}\n\t}\n\n    LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) {\n\t\tnode.graph.beforeChange(/*?*/);\n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tnode.collapse();\n\t\t}\n\t\t\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tnode.graph.afterChange(/*?*/);\n    };\n\n    LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) {\n        node.pin();\n    };\n\n    LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) {\n        new LiteGraph.ContextMenu(\n            LiteGraph.NODE_MODES,\n            { event: e, callback: inner_clicked, parentMenu: menu, node: node }\n        );\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n            var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v);\n            var fApplyMultiNode = function(node){\n\t\t\t\tif (kV>=0 && LiteGraph.NODE_MODES[kV])\n\t\t\t\t\tnode.changeMode(kV);\n\t\t\t\telse{\n\t\t\t\t\tconsole.warn(\"unexpected mode: \"+v);\n\t\t\t\t\tnode.changeMode(LiteGraph.ALWAYS);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyMultiNode(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node for color\";\n        }\n\n        var values = [];\n        values.push({\n            value: null,\n            content:\n                \"<span style='display: block; padding-left: 4px;'>No color</span>\"\n        });\n\n        for (var i in LGraphCanvas.node_colors) {\n            var color = LGraphCanvas.node_colors[i];\n            var value = {\n                value: i,\n                content:\n                    \"<span style='display: block; color: #999; padding-left: 4px; border-left: 8px solid \" +\n                    color.color +\n                    \"; background-color:\" +\n                    color.bgcolor +\n                    \"'>\" +\n                    i +\n                    \"</span>\"\n            };\n            values.push(value);\n        }\n        new LiteGraph.ContextMenu(values, {\n            event: e,\n            callback: inner_clicked,\n            parentMenu: menu,\n            node: node\n        });\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n\n            var color = v.value ? LGraphCanvas.node_colors[v.value] : null;\n\t\t\t\n\t\t\tvar fApplyColor = function(node){\n\t\t\t\tif (color) {\n\t\t\t\t\tif (node.constructor === LiteGraph.LGraphGroup) {\n\t\t\t\t\t\tnode.color = color.groupcolor;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnode.color = color.color;\n\t\t\t\t\t\tnode.bgcolor = color.bgcolor;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tdelete node.color;\n\t\t\t\t\tdelete node.bgcolor;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyColor(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyColor(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n            node.setDirtyCanvas(true, true);\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node passed\";\n        }\n\n        new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, {\n            event: e,\n            callback: inner_clicked,\n            parentMenu: menu,\n            node: node\n        });\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n\t\t\tnode.graph.beforeChange(/*?*/); //node\n            \n\t\t\tvar fApplyMultiNode = function(node){\n\t\t\t\tnode.shape = v;\n\t\t\t}\n\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyMultiNode(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tnode.graph.afterChange(/*?*/); //node\n            node.setDirtyCanvas(true);\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node passed\";\n        }\n\n\t\tvar graph = node.graph;\n\t\tgraph.beforeChange();\n        \n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tif (node.removable === false) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tgraph.remove(node);\n\t\t}\n\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tgraph.afterChange();\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) {\n\t\tvar graph = node.graph;\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif(!graphcanvas) //??\n\t\t\treturn;\n\n\t\tvar nodes_list = Object.values( graphcanvas.selected_nodes || {} );\n\t\tif( !nodes_list.length )\n\t\t\tnodes_list = [ node ];\n\n\t\tvar subgraph_node = LiteGraph.createNode(\"graph/subgraph\");\n\t\tsubgraph_node.pos = node.pos.concat();\n\t\tgraph.add(subgraph_node);\n\n\t\tsubgraph_node.buildFromNodes( nodes_list );\n\n\t\tgraphcanvas.deselectAllNodes();\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) {\n        \n\t\tnode.graph.beforeChange();\n        \n\t\tvar newSelected = {};\n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tif (node.clonable === false) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar newnode = node.clone();\n\t\t\tif (!newnode) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tnewnode.pos = [node.pos[0] + 5, node.pos[1] + 5];\n\t\t\tnode.graph.add(newnode);\n\t\t\tnewSelected[newnode.id] = newnode;\n\t\t}\n\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tif(Object.keys(newSelected).length){\n\t\t\tgraphcanvas.selectNodes(newSelected);\n\t\t}\n\t\t\n\t\tnode.graph.afterChange();\n\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.node_colors = {\n        red: { color: \"#322\", bgcolor: \"#533\", groupcolor: \"#A88\" },\n        brown: { color: \"#332922\", bgcolor: \"#593930\", groupcolor: \"#b06634\" },\n        green: { color: \"#232\", bgcolor: \"#353\", groupcolor: \"#8A8\" },\n        blue: { color: \"#223\", bgcolor: \"#335\", groupcolor: \"#88A\" },\n        pale_blue: {\n            color: \"#2a363b\",\n            bgcolor: \"#3f5159\",\n            groupcolor: \"#3f789e\"\n        },\n        cyan: { color: \"#233\", bgcolor: \"#355\", groupcolor: \"#8AA\" },\n        purple: { color: \"#323\", bgcolor: \"#535\", groupcolor: \"#a1309b\" },\n        yellow: { color: \"#432\", bgcolor: \"#653\", groupcolor: \"#b58b2a\" },\n        black: { color: \"#222\", bgcolor: \"#000\", groupcolor: \"#444\" }\n    };\n\n    LGraphCanvas.prototype.getCanvasMenuOptions = function() {\n        var options = null;\n\t\tvar that = this;\n        if (this.getMenuOptions) {\n            options = this.getMenuOptions();\n        } else {\n            options = [\n                {\n                    content: \"Add Node\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuAdd\n                },\n                { content: \"Add Group\", callback: LGraphCanvas.onGroupAdd },\n\t\t\t\t//{ content: \"Arrange\", callback: that.graph.arrange },\n                //{content:\"Collapse All\", callback: LGraphCanvas.onMenuCollapseAll }\n            ];\n            /*if (LiteGraph.showCanvasOptions){\n                options.push({ content: \"Options\", callback: that.showShowGraphOptionsPanel });\n            }*/\n\n            if (Object.keys(this.selected_nodes).length > 1) {\n                options.push({\n                    content: \"Align\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onGroupAlign,\n                })\n            }\n\n            if (this._graph_stack && this._graph_stack.length > 0) {\n                options.push(null, {\n                    content: \"Close subgraph\",\n                    callback: this.closeSubgraph.bind(this)\n                });\n            }\n        }\n\n        if (this.getExtraMenuOptions) {\n            var extra = this.getExtraMenuOptions(this, options);\n            if (extra) {\n                options = options.concat(extra);\n            }\n        }\n\n        return options;\n    };\n\n    //called by processContextMenu to extract the menu list\n    LGraphCanvas.prototype.getNodeMenuOptions = function(node) {\n        var options = null;\n\n        if (node.getMenuOptions) {\n            options = node.getMenuOptions(this);\n        } else {\n            options = [\n                {\n                    content: \"Inputs\",\n                    has_submenu: true,\n                    disabled: true,\n                    callback: LGraphCanvas.showMenuNodeOptionalInputs\n                },\n                {\n                    content: \"Outputs\",\n                    has_submenu: true,\n                    disabled: true,\n                    callback: LGraphCanvas.showMenuNodeOptionalOutputs\n                },\n                null,\n                {\n                    content: \"Properties\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onShowMenuNodeProperties\n                },\n                null,\n                {\n                    content: \"Title\",\n                    callback: LGraphCanvas.onShowPropertyEditor\n                },\n                {\n                    content: \"Mode\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeMode\n                }];\n            if(node.resizable !== false){\n                options.push({\n                    content: \"Resize\", callback: LGraphCanvas.onMenuResizeNode\n                });\n            }\n            options.push(\n                {\n                    content: \"Collapse\",\n                    callback: LGraphCanvas.onMenuNodeCollapse\n                },\n                { content: \"Pin\", callback: LGraphCanvas.onMenuNodePin },\n                {\n                    content: \"Colors\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeColors\n                },\n                {\n                    content: \"Shapes\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeShapes\n                },\n                null\n            );\n        }\n\n        if (node.onGetInputs) {\n            var inputs = node.onGetInputs();\n            if (inputs && inputs.length) {\n                options[0].disabled = false;\n            }\n        }\n\n        if (node.onGetOutputs) {\n            var outputs = node.onGetOutputs();\n            if (outputs && outputs.length) {\n                options[1].disabled = false;\n            }\n        }\n\n        if (node.getExtraMenuOptions) {\n            var extra = node.getExtraMenuOptions(this, options);\n            if (extra) {\n                extra.push(null);\n                options = extra.concat(options);\n            }\n        }\n\n        if (node.clonable !== false) {\n            options.push({\n                content: \"Clone\",\n                callback: LGraphCanvas.onMenuNodeClone\n            });\n        }\n\n\t\tif(0) //TODO\n\t\toptions.push({\n\t\t\tcontent: \"To Subgraph\",\n\t\t\tcallback: LGraphCanvas.onMenuNodeToSubgraph\n\t\t});\n\n        if (Object.keys(this.selected_nodes).length > 1) {\n            options.push({\n                content: \"Align Selected To\",\n                has_submenu: true,\n                callback: LGraphCanvas.onNodeAlign,\n            })\n        }\n\n\t\toptions.push(null, {\n\t\t\tcontent: \"Remove\",\n\t\t\tdisabled: !(node.removable !== false && !node.block_delete ),\n\t\t\tcallback: LGraphCanvas.onMenuNodeRemove\n\t\t});\n\n        if (node.graph && node.graph.onGetNodeMenuOptions) {\n            node.graph.onGetNodeMenuOptions(options, node);\n        }\n\n        return options;\n    };\n\n    LGraphCanvas.prototype.getGroupMenuOptions = function(node) {\n        var o = [\n            { content: \"Title\", callback: LGraphCanvas.onShowPropertyEditor },\n            {\n                content: \"Color\",\n                has_submenu: true,\n                callback: LGraphCanvas.onMenuNodeColors\n            },\n            {\n                content: \"Font size\",\n                property: \"font_size\",\n                type: \"Number\",\n                callback: LGraphCanvas.onShowPropertyEditor\n            },\n            null,\n            { content: \"Remove\", callback: LGraphCanvas.onMenuNodeRemove }\n        ];\n\n        return o;\n    };\n\n    LGraphCanvas.prototype.processContextMenu = function(node, event) {\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var menu_info = null;\n        var options = {\n            event: event,\n            callback: inner_option_clicked,\n            extra: node\n        };\n\n\t\tif(node)\n\t\t\toptions.title = node.type;\n\n        //check if mouse is in input\n        var slot = null;\n        if (node) {\n            slot = node.getSlotInPosition(event.canvasX, event.canvasY);\n            LGraphCanvas.active_node = node;\n        }\n\n        if (slot) {\n            //on slot\n            menu_info = [];\n            if (node.getSlotMenuOptions) {\n                menu_info = node.getSlotMenuOptions(slot);\n            } else {\n                if (\n                    slot &&\n                    slot.output &&\n                    slot.output.links &&\n                    slot.output.links.length\n                ) {\n                    menu_info.push({ content: \"Disconnect Links\", slot: slot });\n                }\n                var _slot = slot.input || slot.output;\n                if (_slot.removable){\n                \tmenu_info.push(\n\t                    _slot.locked\n\t                        ? \"Cannot remove\"\n\t                        : { content: \"Remove Slot\", slot: slot }\n\t                );\n            \t}\n                if (!_slot.nameLocked){\n\t                menu_info.push({ content: \"Rename Slot\", slot: slot });\n                }\n    \n            }\n            options.title =\n                (slot.input ? slot.input.type : slot.output.type) || \"*\";\n            if (slot.input && slot.input.type == LiteGraph.ACTION) {\n                options.title = \"Action\";\n            }\n            if (slot.output && slot.output.type == LiteGraph.EVENT) {\n                options.title = \"Event\";\n            }\n        } else {\n            if (node) {\n                //on node\n                menu_info = this.getNodeMenuOptions(node);\n            } else {\n                menu_info = this.getCanvasMenuOptions();\n                var group = this.graph.getGroupOnPos(\n                    event.canvasX,\n                    event.canvasY\n                );\n                if (group) {\n                    //on group\n                    menu_info.push(null, {\n                        content: \"Edit Group\",\n                        has_submenu: true,\n                        submenu: {\n                            title: \"Group\",\n                            extra: group,\n                            options: this.getGroupMenuOptions(group)\n                        }\n                    });\n                }\n            }\n        }\n\n        //show menu\n        if (!menu_info) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window);\n\n        function inner_option_clicked(v, options, e) {\n            if (!v) {\n                return;\n            }\n\n            if (v.content == \"Remove Slot\") {\n                var info = v.slot;\n                node.graph.beforeChange();\n                if (info.input) {\n                    node.removeInput(info.slot);\n                } else if (info.output) {\n                    node.removeOutput(info.slot);\n                }\n                node.graph.afterChange();\n                return;\n            } else if (v.content == \"Disconnect Links\") {\n                var info = v.slot;\n                node.graph.beforeChange();\n                if (info.output) {\n                    node.disconnectOutput(info.slot);\n                } else if (info.input) {\n                    node.disconnectInput(info.slot);\n                }\n                node.graph.afterChange();\n                return;\n            } else if (v.content == \"Rename Slot\") {\n                var info = v.slot;\n                var slot_info = info.input\n                    ? node.getInputInfo(info.slot)\n                    : node.getOutputInfo(info.slot);\n                var dialog = that.createDialog(\n                    \"<span class='name'>Name</span><input autofocus type='text'/><button>OK</button>\",\n                    options\n                );\n                var input = dialog.querySelector(\"input\");\n                if (input && slot_info) {\n                    input.value = slot_info.label || \"\";\n                }\n                var inner = function(){\n                \tnode.graph.beforeChange();\n                    if (input.value) {\n                        if (slot_info) {\n                            slot_info.label = input.value;\n                        }\n                        that.setDirty(true);\n                    }\n                    dialog.close();\n                    node.graph.afterChange();\n                }\n                dialog.querySelector(\"button\").addEventListener(\"click\", inner);\n                input.addEventListener(\"keydown\", function(e) {\n                    dialog.is_modified = true;\n                    if (e.keyCode == 27) {\n                        //ESC\n                        dialog.close();\n                    } else if (e.keyCode == 13) {\n                        inner(); // save\n                    } else if (e.keyCode != 13 && e.target.localName != \"textarea\") {\n                        return;\n                    }\n                    e.preventDefault();\n                    e.stopPropagation();\n                });\n                input.focus();\n            }\n\n            //if(v.callback)\n            //\treturn v.callback.call(that, node, options, e, menu, that, event );\n        }\n    };\n\n    //API *************************************************\n    function compareObjects(a, b) {\n        for (var i in a) {\n            if (a[i] != b[i]) {\n                return false;\n            }\n        }\n        return true;\n    }\n    LiteGraph.compareObjects = compareObjects;\n\n    function distance(a, b) {\n        return Math.sqrt(\n            (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])\n        );\n    }\n    LiteGraph.distance = distance;\n\n    function colorToString(c) {\n        return (\n            \"rgba(\" +\n            Math.round(c[0] * 255).toFixed() +\n            \",\" +\n            Math.round(c[1] * 255).toFixed() +\n            \",\" +\n            Math.round(c[2] * 255).toFixed() +\n            \",\" +\n            (c.length == 4 ? c[3].toFixed(2) : \"1.0\") +\n            \")\"\n        );\n    }\n    LiteGraph.colorToString = colorToString;\n\n    function isInsideRectangle(x, y, left, top, width, height) {\n        if (left < x && left + width > x && top < y && top + height > y) {\n            return true;\n        }\n        return false;\n    }\n    LiteGraph.isInsideRectangle = isInsideRectangle;\n\n    //[minx,miny,maxx,maxy]\n    function growBounding(bounding, x, y) {\n        if (x < bounding[0]) {\n            bounding[0] = x;\n        } else if (x > bounding[2]) {\n            bounding[2] = x;\n        }\n\n        if (y < bounding[1]) {\n            bounding[1] = y;\n        } else if (y > bounding[3]) {\n            bounding[3] = y;\n        }\n    }\n    LiteGraph.growBounding = growBounding;\n\n    //point inside bounding box\n    function isInsideBounding(p, bb) {\n        if (\n            p[0] < bb[0][0] ||\n            p[1] < bb[0][1] ||\n            p[0] > bb[1][0] ||\n            p[1] > bb[1][1]\n        ) {\n            return false;\n        }\n        return true;\n    }\n    LiteGraph.isInsideBounding = isInsideBounding;\n\n    //bounding overlap, format: [ startx, starty, width, height ]\n    function overlapBounding(a, b) {\n        var A_end_x = a[0] + a[2];\n        var A_end_y = a[1] + a[3];\n        var B_end_x = b[0] + b[2];\n        var B_end_y = b[1] + b[3];\n\n        if (\n            a[0] > B_end_x ||\n            a[1] > B_end_y ||\n            A_end_x < b[0] ||\n            A_end_y < b[1]\n        ) {\n            return false;\n        }\n        return true;\n    }\n    LiteGraph.overlapBounding = overlapBounding;\n\n    //Convert a hex value to its decimal value - the inputted hex must be in the\n    //\tformat of a hex triplet - the kind we use for HTML colours. The function\n    //\twill return an array with three values.\n    function hex2num(hex) {\n        if (hex.charAt(0) == \"#\") {\n            hex = hex.slice(1);\n        } //Remove the '#' char - if there is one.\n        hex = hex.toUpperCase();\n        var hex_alphabets = \"0123456789ABCDEF\";\n        var value = new Array(3);\n        var k = 0;\n        var int1, int2;\n        for (var i = 0; i < 6; i += 2) {\n            int1 = hex_alphabets.indexOf(hex.charAt(i));\n            int2 = hex_alphabets.indexOf(hex.charAt(i + 1));\n            value[k] = int1 * 16 + int2;\n            k++;\n        }\n        return value;\n    }\n\n    LiteGraph.hex2num = hex2num;\n\n    //Give a array with three values as the argument and the function will return\n    //\tthe corresponding hex triplet.\n    function num2hex(triplet) {\n        var hex_alphabets = \"0123456789ABCDEF\";\n        var hex = \"#\";\n        var int1, int2;\n        for (var i = 0; i < 3; i++) {\n            int1 = triplet[i] / 16;\n            int2 = triplet[i] % 16;\n\n            hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2);\n        }\n        return hex;\n    }\n\n    LiteGraph.num2hex = num2hex;\n\n    /* LiteGraph GUI elements used for canvas editing *************************************/\n\n    /**\n     * ContextMenu from LiteGUI\n     *\n     * @class ContextMenu\n     * @constructor\n     * @param {Array} values (allows object { title: \"Nice text\", callback: function ... })\n     * @param {Object} options [optional] Some options:\\\n     * - title: title to show on top of the menu\n     * - callback: function to call when an option is clicked, it receives the item information\n     * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback\n     * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position\n     */\n    function ContextMenu(values, options) {\n        options = options || {};\n        this.options = options;\n        var that = this;\n\n        //to link a menu with its parent\n        if (options.parentMenu) {\n            if (options.parentMenu.constructor !== this.constructor) {\n                console.error(\n                    \"parentMenu must be of class ContextMenu, ignoring it\"\n                );\n                options.parentMenu = null;\n            } else {\n                this.parentMenu = options.parentMenu;\n                this.parentMenu.lock = true;\n                this.parentMenu.current_submenu = this;\n            }\n        }\n\n\t\tvar eventClass = null;\n\t\tif(options.event) //use strings because comparing classes between windows doesnt work\n\t\t\teventClass = options.event.constructor.name;\n        if ( eventClass !== \"MouseEvent\" &&\n            eventClass !== \"CustomEvent\" &&\n\t\t\teventClass !== \"PointerEvent\"\n        ) {\n            console.error(\n                \"Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. (\"+eventClass+\")\"\n            );\n            options.event = null;\n        }\n\n        var root = document.createElement(\"div\");\n        root.className = \"litegraph litecontextmenu litemenubar-panel\";\n        if (options.className) {\n            root.className += \" \" + options.className;\n        }\n        root.style.minWidth = 100;\n        root.style.minHeight = 100;\n        root.style.pointerEvents = \"none\";\n        setTimeout(function() {\n            root.style.pointerEvents = \"auto\";\n        }, 100); //delay so the mouse up event is not caught by this element\n\n        //this prevents the default context browser menu to open in case this menu was created when pressing right button\n\t\tLiteGraph.pointerListenerAdd(root,\"up\",\n            function(e) {\n\t\t\t  \t//console.log(\"pointerevents: ContextMenu up root prevent\");\n                e.preventDefault();\n                return true;\n            },\n            true\n        );\n        root.addEventListener(\n            \"contextmenu\",\n            function(e) {\n                if (e.button != 2) {\n                    //right button\n                    return false;\n                }\n                e.preventDefault();\n                return false;\n            },\n            true\n        );\n\n        LiteGraph.pointerListenerAdd(root,\"down\",\n            function(e) {\n\t\t\t  \t//console.log(\"pointerevents: ContextMenu down\");\n                if (e.button == 2) {\n                    that.close();\n                    e.preventDefault();\n                    return true;\n                }\n            },\n            true\n        );\n\n        function on_mouse_wheel(e) {\n            var pos = parseInt(root.style.top);\n            root.style.top =\n                (pos + e.deltaY * options.scroll_speed).toFixed() + \"px\";\n            e.preventDefault();\n            return true;\n        }\n\n        if (!options.scroll_speed) {\n            options.scroll_speed = 0.1;\n        }\n\n        root.addEventListener(\"wheel\", on_mouse_wheel, true);\n        root.addEventListener(\"mousewheel\", on_mouse_wheel, true);\n\n        this.root = root;\n\n        //title\n        if (options.title) {\n            var element = document.createElement(\"div\");\n            element.className = \"litemenu-title\";\n            element.innerHTML = options.title;\n            root.appendChild(element);\n        }\n\n        //entries\n        var num = 0;\n        for (var i=0; i < values.length; i++) {\n            var name = values.constructor == Array ? values[i] : i;\n            if (name != null && name.constructor !== String) {\n                name = name.content === undefined ? String(name) : name.content;\n            }\n            var value = values[i];\n            this.addItem(name, value, options);\n            num++;\n        }\n\n        //close on leave? touch enabled devices won't work TODO use a global device detector and condition on that\n        /*LiteGraph.pointerListenerAdd(root,\"leave\", function(e) {\n\t\t  \tconsole.log(\"pointerevents: ContextMenu leave\");\n            if (that.lock) {\n                return;\n            }\n            if (root.closing_timer) {\n                clearTimeout(root.closing_timer);\n            }\n            root.closing_timer = setTimeout(that.close.bind(that, e), 500);\n            //that.close(e);\n        });*/\n\n\t\tLiteGraph.pointerListenerAdd(root,\"enter\", function(e) {\n\t\t  \t//console.log(\"pointerevents: ContextMenu enter\");\n            if (root.closing_timer) {\n                clearTimeout(root.closing_timer);\n            }\n        });\n\n        //insert before checking position\n        var root_document = document;\n        if (options.event) {\n            root_document = options.event.target.ownerDocument;\n        }\n\n        if (!root_document) {\n            root_document = document;\n        }\n\n\t\tif( root_document.fullscreenElement )\n\t        root_document.fullscreenElement.appendChild(root);\n\t\telse\n\t\t    root_document.body.appendChild(root);\n\n        //compute best position\n        var left = options.left || 0;\n        var top = options.top || 0;\n        if (options.event) {\n            left = options.event.clientX - 10;\n            top = options.event.clientY - 10;\n            if (options.title) {\n                top -= 20;\n            }\n\n            if (options.parentMenu) {\n                var rect = options.parentMenu.root.getBoundingClientRect();\n                left = rect.left + rect.width;\n            }\n\n            var body_rect = document.body.getBoundingClientRect();\n            var root_rect = root.getBoundingClientRect();\n\t\t\tif(body_rect.height == 0)\n\t\t\t\tconsole.error(\"document.body height is 0. That is dangerous, set html,body { height: 100%; }\");\n\n            if (body_rect.width && left > body_rect.width - root_rect.width - 10) {\n                left = body_rect.width - root_rect.width - 10;\n            }\n            if (body_rect.height && top > body_rect.height - root_rect.height - 10) {\n                top = body_rect.height - root_rect.height - 10;\n            }\n        }\n\n        root.style.left = left + \"px\";\n        root.style.top = top + \"px\";\n\n        if (options.scale) {\n            root.style.transform = \"scale(\" + options.scale + \")\";\n        }\n    }\n\n    ContextMenu.prototype.addItem = function(name, value, options) {\n        var that = this;\n        options = options || {};\n\n        var element = document.createElement(\"div\");\n        element.className = \"litemenu-entry submenu\";\n\n        var disabled = false;\n\n        if (value === null) {\n            element.classList.add(\"separator\");\n            //element.innerHTML = \"<hr/>\"\n            //continue;\n        } else {\n            element.innerHTML = value && value.title ? value.title : name;\n            element.value = value;\n\n            if (value) {\n                if (value.disabled) {\n                    disabled = true;\n                    element.classList.add(\"disabled\");\n                }\n                if (value.submenu || value.has_submenu) {\n                    element.classList.add(\"has_submenu\");\n                }\n            }\n\n            if (typeof value == \"function\") {\n                element.dataset[\"value\"] = name;\n                element.onclick_callback = value;\n            } else {\n                element.dataset[\"value\"] = value;\n            }\n\n            if (value.className) {\n                element.className += \" \" + value.className;\n            }\n        }\n\n        this.root.appendChild(element);\n        if (!disabled) {\n            element.addEventListener(\"click\", inner_onclick);\n        }\n        if (!disabled && options.autoopen) {\n\t\t\tLiteGraph.pointerListenerAdd(element,\"enter\",inner_over);\n        }\n\n        function inner_over(e) {\n            var value = this.value;\n            if (!value || !value.has_submenu) {\n                return;\n            }\n            //if it is a submenu, autoopen like the item was clicked\n            inner_onclick.call(this, e);\n        }\n\n        //menu option clicked\n        function inner_onclick(e) {\n            var value = this.value;\n            var close_parent = true;\n\n            if (that.current_submenu) {\n                that.current_submenu.close(e);\n            }\n\n            //global callback\n            if (options.callback) {\n                var r = options.callback.call(\n                    this,\n                    value,\n                    options,\n                    e,\n                    that,\n                    options.node\n                );\n                if (r === true) {\n                    close_parent = false;\n                }\n            }\n\n            //special cases\n            if (value) {\n                if (\n                    value.callback &&\n                    !options.ignore_item_callbacks &&\n                    value.disabled !== true\n                ) {\n                    //item callback\n                    var r = value.callback.call(\n                        this,\n                        value,\n                        options,\n                        e,\n                        that,\n                        options.extra\n                    );\n                    if (r === true) {\n                        close_parent = false;\n                    }\n                }\n                if (value.submenu) {\n                    if (!value.submenu.options) {\n                        throw \"ContextMenu submenu needs options\";\n                    }\n                    var submenu = new that.constructor(value.submenu.options, {\n                        callback: value.submenu.callback,\n                        event: e,\n                        parentMenu: that,\n                        ignore_item_callbacks:\n                            value.submenu.ignore_item_callbacks,\n                        title: value.submenu.title,\n                        extra: value.submenu.extra,\n                        autoopen: options.autoopen\n                    });\n                    close_parent = false;\n                }\n            }\n\n            if (close_parent && !that.lock) {\n                that.close();\n            }\n        }\n\n        return element;\n    };\n\n    ContextMenu.prototype.close = function(e, ignore_parent_menu) {\n        if (this.root.parentNode) {\n            this.root.parentNode.removeChild(this.root);\n        }\n        if (this.parentMenu && !ignore_parent_menu) {\n            this.parentMenu.lock = false;\n            this.parentMenu.current_submenu = null;\n            if (e === undefined) {\n                this.parentMenu.close();\n            } else if (\n                e &&\n                !ContextMenu.isCursorOverElement(e, this.parentMenu.root)\n            ) {\n                ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method+\"leave\", e);\n            }\n        }\n        if (this.current_submenu) {\n            this.current_submenu.close(e, true);\n        }\n\n        if (this.root.closing_timer) {\n            clearTimeout(this.root.closing_timer);\n        }\n        \n        // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu\n        // on key press, allow filtering/selecting the context menu elements\n    };\n\n    //this code is used to trigger events easily (used in the context menu mouseleave\n    ContextMenu.trigger = function(element, event_name, params, origin) {\n        var evt = document.createEvent(\"CustomEvent\");\n        evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail\n        evt.srcElement = origin;\n        if (element.dispatchEvent) {\n            element.dispatchEvent(evt);\n        } else if (element.__events) {\n            element.__events.dispatchEvent(evt);\n        }\n        //else nothing seems binded here so nothing to do\n        return evt;\n    };\n\n    //returns the top most menu\n    ContextMenu.prototype.getTopMenu = function() {\n        if (this.options.parentMenu) {\n            return this.options.parentMenu.getTopMenu();\n        }\n        return this;\n    };\n\n    ContextMenu.prototype.getFirstEvent = function() {\n        if (this.options.parentMenu) {\n            return this.options.parentMenu.getFirstEvent();\n        }\n        return this.options.event;\n    };\n\n    ContextMenu.isCursorOverElement = function(event, element) {\n        var left = event.clientX;\n        var top = event.clientY;\n        var rect = element.getBoundingClientRect();\n        if (!rect) {\n            return false;\n        }\n        if (\n            top > rect.top &&\n            top < rect.top + rect.height &&\n            left > rect.left &&\n            left < rect.left + rect.width\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    LiteGraph.ContextMenu = ContextMenu;\n\n    LiteGraph.closeAllContextMenus = function(ref_window) {\n        ref_window = ref_window || window;\n\n        var elements = ref_window.document.querySelectorAll(\".litecontextmenu\");\n        if (!elements.length) {\n            return;\n        }\n\n        var result = [];\n        for (var i = 0; i < elements.length; i++) {\n            result.push(elements[i]);\n        }\n\n        for (var i=0; i < result.length; i++) {\n            if (result[i].close) {\n                result[i].close();\n            } else if (result[i].parentNode) {\n                result[i].parentNode.removeChild(result[i]);\n            }\n        }\n    };\n\n    LiteGraph.extendClass = function(target, origin) {\n        for (var i in origin) {\n            //copy class properties\n            if (target.hasOwnProperty(i)) {\n                continue;\n            }\n            target[i] = origin[i];\n        }\n\n        if (origin.prototype) {\n            //copy prototype properties\n            for (var i in origin.prototype) {\n                //only enumerable\n                if (!origin.prototype.hasOwnProperty(i)) {\n                    continue;\n                }\n\n                if (target.prototype.hasOwnProperty(i)) {\n                    //avoid overwriting existing ones\n                    continue;\n                }\n\n                //copy getters\n                if (origin.prototype.__lookupGetter__(i)) {\n                    target.prototype.__defineGetter__(\n                        i,\n                        origin.prototype.__lookupGetter__(i)\n                    );\n                } else {\n                    target.prototype[i] = origin.prototype[i];\n                }\n\n                //and setters\n                if (origin.prototype.__lookupSetter__(i)) {\n                    target.prototype.__defineSetter__(\n                        i,\n                        origin.prototype.__lookupSetter__(i)\n                    );\n                }\n            }\n        }\n    };\n\n\t//used by some widgets to render a curve editor\n\tfunction CurveEditor( points )\n\t{\n\t\tthis.points = points;\n\t\tthis.selected = -1;\n\t\tthis.nearest = -1;\n\t\tthis.size = null; //stores last size used\n\t\tthis.must_update = true;\n\t\tthis.margin = 5;\n\t}\n\n\tCurveEditor.sampleCurve = function(f,points)\n\t{\n\t\tif(!points)\n\t\t\treturn;\n\t\tfor(var i = 0; i < points.length - 1; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tvar pn = points[i+1];\n\t\t\tif(pn[0] < f)\n\t\t\t\tcontinue;\n\t\t\tvar r = (pn[0] - p[0]);\n\t\t\tif( Math.abs(r) < 0.00001 )\n\t\t\t\treturn p[1];\n\t\t\tvar local_f = (f - p[0]) / r;\n\t\t\treturn p[1] * (1.0 - local_f) + pn[1] * local_f;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tCurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tthis.size = size;\n\t\tvar w = size[0] - this.margin * 2;\n\t\tvar h = size[1] - this.margin * 2;\n\n\t\tline_color = line_color || \"#666\";\n\n\t\tctx.save();\n\t\tctx.translate(this.margin,this.margin);\n\n\t\tif(background_color)\n\t\t{\n\t\t\tctx.fillStyle = \"#111\";\n\t\t\tctx.fillRect(0,0,w,h);\n\t\t\tctx.fillStyle = \"#222\";\n\t\t\tctx.fillRect(w*0.5,0,1,h);\n\t\t\tctx.strokeStyle = \"#333\";\n\t\t\tctx.strokeRect(0,0,w,h);\n\t\t}\n\t\tctx.strokeStyle = line_color;\n\t\tif(inactive)\n\t\t\tctx.globalAlpha = 0.5;\n\t\tctx.beginPath();\n\t\tfor(var i = 0; i < points.length; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tctx.lineTo( p[0] * w, (1.0 - p[1]) * h );\n\t\t}\n\t\tctx.stroke();\n\t\tctx.globalAlpha = 1;\n\t\tif(!inactive)\n\t\t\tfor(var i = 0; i < points.length; ++i)\n\t\t\t{\n\t\t\t\tvar p = points[i];\n\t\t\t\tctx.fillStyle = this.selected == i ? \"#FFF\" : (this.nearest == i ? \"#DDD\" : \"#AAA\");\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 );\n\t\t\t\tctx.fill();\n\t\t\t}\n\t\tctx.restore();\n\t}\n\n\t//localpos is mouse in curve editor space\n\tCurveEditor.prototype.onMouseDown = function( localpos, graphcanvas )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tif( localpos[1] < 0 )\n\t\t\treturn;\n\n\t\t//this.captureInput(true);\n\t\tvar w = this.size[0] - this.margin * 2;\n\t\tvar h = this.size[1] - this.margin * 2;\n\t\tvar x = localpos[0] - this.margin;\n\t\tvar y = localpos[1] - this.margin;\n\t\tvar pos = [x,y];\n\t\tvar max_dist = 30 / graphcanvas.ds.scale;\n\t\t//search closer one\n\t\tthis.selected = this.getCloserPoint(pos, max_dist);\n\t\t//create one\n\t\tif(this.selected == -1)\n\t\t{\n\t\t\tvar point = [x / w, 1 - y / h];\n\t\t\tpoints.push(point);\n\t\t\tpoints.sort(function(a,b){ return a[0] - b[0]; });\n\t\t\tthis.selected = points.indexOf(point);\n\t\t\tthis.must_update = true;\n\t\t}\n\t\tif(this.selected != -1)\n\t\t\treturn true;\n\t}\n\n\tCurveEditor.prototype.onMouseMove = function( localpos, graphcanvas )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tvar s = this.selected;\n\t\tif(s < 0)\n\t\t\treturn;\n\t\tvar x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 );\n\t\tvar y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 );\n\t\tvar curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)];\n\t\tvar max_dist = 30 / graphcanvas.ds.scale;\n\t\tthis._nearest = this.getCloserPoint(curvepos, max_dist);\n\t\tvar point = points[s];\n\t\tif(point)\n\t\t{\n\t\t\tvar is_edge_point = s == 0 || s == points.length - 1;\n\t\t\tif( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) )\n\t\t\t{\n\t\t\t\tpoints.splice(s,1);\n\t\t\t\tthis.selected = -1;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif( !is_edge_point ) //not edges\n\t\t\t\tpoint[0] = clamp(x, 0, 1);\n\t\t\telse\n\t\t\t\tpoint[0] = s == 0 ? 0 : 1;\n\t\t\tpoint[1] = 1.0 - clamp(y, 0, 1);\n\t\t\tpoints.sort(function(a,b){ return a[0] - b[0]; });\n\t\t\tthis.selected = points.indexOf(point);\n\t\t\tthis.must_update = true;\n\t\t}\n\t}\n\n\tCurveEditor.prototype.onMouseUp = function( localpos, graphcanvas )\n\t{\n\t\tthis.selected = -1;\n\t\treturn false;\n\t}\n\n\tCurveEditor.prototype.getCloserPoint = function(pos, max_dist)\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn -1;\n\t\tmax_dist = max_dist || 30;\n\t\tvar w = (this.size[0] - this.margin * 2);\n\t\tvar h = (this.size[1] - this.margin * 2);\n\t\tvar num = points.length;\n\t\tvar p2 = [0,0];\n\t\tvar min_dist = 1000000;\n\t\tvar closest = -1;\n\t\tvar last_valid = -1;\n\t\tfor(var i = 0; i < num; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tp2[0] = p[0] * w;\n\t\t\tp2[1] = (1.0 - p[1]) * h;\n\t\t\tif(p2[0] < pos[0])\n\t\t\t\tlast_valid = i;\n\t\t\tvar dist = vec2.distance(pos,p2);\n\t\t\tif(dist > min_dist || dist > max_dist)\n\t\t\t\tcontinue;\n\t\t\tclosest = i;\n\t\t\tmin_dist = dist;\n\t\t}\n\t\treturn closest;\n\t}\n\n\tLiteGraph.CurveEditor = CurveEditor;\n\n    //used to create nodes from wrapping functions\n    LiteGraph.getParameterNames = function(func) {\n        return (func + \"\")\n            .replace(/[/][/].*$/gm, \"\") // strip single-line comments\n            .replace(/\\s+/g, \"\") // strip white space\n            .replace(/[/][*][^/*]*[*][/]/g, \"\") // strip multi-line comments  /**/\n            .split(\"){\", 1)[0]\n            .replace(/^[^(]*[(]/, \"\") // extract the parameters\n            .replace(/=[^,]+/g, \"\") // strip any ES6 defaults\n            .split(\",\")\n            .filter(Boolean); // split & filter [\"\"]\n    };\n\n\t/* helper for interaction: pointer, touch, mouse Listeners\n\tused by LGraphCanvas DragAndScale ContextMenu*/\n\tLiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) {\n\t\tif (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!==\"function\"){\n\t\t\t//console.log(\"cant pointerListenerAdd \"+oDOM+\", \"+sEvent+\", \"+fCall);\n\t\t\treturn; // -- break --\n\t\t}\n\t\t\n\t\tvar sMethod = LiteGraph.pointerevents_method;\n\t\tvar sEvent = sEvIn;\n\t\t\n\t\t// UNDER CONSTRUCTION\n\t\t// convert pointerevents to touch event when not available\n\t\tif (sMethod==\"pointer\" && !window.PointerEvent){ \n\t\t\tconsole.warn(\"sMethod=='pointer' && !window.PointerEvent\");\n\t\t\tconsole.log(\"Converting pointer[\"+sEvent+\"] : down move up cancel enter TO touchstart touchmove touchend, etc ..\");\n\t\t\tswitch(sEvent){\n\t\t\t\tcase \"down\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\tsEvent = \"start\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"move\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\t//sEvent = \"move\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"up\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\tsEvent = \"end\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"cancel\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\t//sEvent = \"cancel\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"enter\":{\n\t\t\t\t\tconsole.log(\"debug: Should I send a move event?\"); // ???\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// case \"over\": case \"out\": not used at now\n\t\t\t\tdefault:{\n\t\t\t\t\tconsole.warn(\"PointerEvent not available in this browser ? The event \"+sEvent+\" would not be called\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tswitch(sEvent){\n\t\t\t//both pointer and move events\n\t\t\tcase \"down\": case \"up\": case \"move\": case \"over\": case \"out\": case \"enter\":\n\t\t\t{\n\t\t\t\toDOM.addEventListener(sMethod+sEvent, fCall, capture);\n\t\t\t}\n\t\t\t// only pointerevents\n\t\t\tcase \"leave\": case \"cancel\": case \"gotpointercapture\": case \"lostpointercapture\":\n\t\t\t{\n\t\t\t\tif (sMethod!=\"mouse\"){\n\t\t\t\t\treturn oDOM.addEventListener(sMethod+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// not \"pointer\" || \"mouse\"\n\t\t\tdefault:\n\t\t\t\treturn oDOM.addEventListener(sEvent, fCall, capture);\n\t\t}\n\t}\n\tLiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) {\n\t\tif (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!==\"function\"){\n\t\t\t//console.log(\"cant pointerListenerRemove \"+oDOM+\", \"+sEvent+\", \"+fCall);\n\t\t\treturn; // -- break --\n\t\t}\n\t\tswitch(sEvent){\n\t\t\t//both pointer and move events\n\t\t\tcase \"down\": case \"up\": case \"move\": case \"over\": case \"out\": case \"enter\":\n\t\t\t{\n\t\t\t\tif (LiteGraph.pointerevents_method==\"pointer\" || LiteGraph.pointerevents_method==\"mouse\"){\n\t\t\t\t\toDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// only pointerevents\n\t\t\tcase \"leave\": case \"cancel\": case \"gotpointercapture\": case \"lostpointercapture\":\n\t\t\t{\n\t\t\t\tif (LiteGraph.pointerevents_method==\"pointer\"){\n\t\t\t\t\treturn oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// not \"pointer\" || \"mouse\"\n\t\t\tdefault:\n\t\t\t\treturn oDOM.removeEventListener(sEvent, fCall, capture);\n\t\t}\n\t}\n\n    function clamp(v, a, b) {\n        return a > v ? a : b < v ? b : v;\n    };\n    global.clamp = clamp;\n\n    if (typeof window != \"undefined\" && !window[\"requestAnimationFrame\"]) {\n        window.requestAnimationFrame =\n            window.webkitRequestAnimationFrame ||\n            window.mozRequestAnimationFrame ||\n            function(callback) {\n                window.setTimeout(callback, 1000 / 60);\n            };\n    }\n})(this);\n\nif (typeof exports != \"undefined\") {\n    exports.LiteGraph = this.LiteGraph;\n    exports.LGraph = this.LGraph;\n    exports.LLink = this.LLink;\n    exports.LGraphNode = this.LGraphNode;\n    exports.LGraphGroup = this.LGraphGroup;\n    exports.DragAndScale = this.DragAndScale;\n    exports.LGraphCanvas = this.LGraphCanvas;\n    exports.ContextMenu = this.ContextMenu;\n}\n\n\n"
  },
  {
    "path": "build/litegraph.js",
    "content": "//packer version\n\n\n(function(global) {\n    // *************************************************************\n    //   LiteGraph CLASS                                     *******\n    // *************************************************************\n\n    /**\n     * The Global Scope. It contains all the registered node classes.\n     *\n     * @class LiteGraph\n     * @constructor\n     */\n\n    var LiteGraph = (global.LiteGraph = {\n        VERSION: 0.4,\n\n        CANVAS_GRID_SIZE: 10,\n\n        NODE_TITLE_HEIGHT: 30,\n        NODE_TITLE_TEXT_Y: 20,\n        NODE_SLOT_HEIGHT: 20,\n        NODE_WIDGET_HEIGHT: 20,\n        NODE_WIDTH: 140,\n        NODE_MIN_WIDTH: 50,\n        NODE_COLLAPSED_RADIUS: 10,\n        NODE_COLLAPSED_WIDTH: 80,\n        NODE_TITLE_COLOR: \"#999\",\n        NODE_SELECTED_TITLE_COLOR: \"#FFF\",\n        NODE_TEXT_SIZE: 14,\n        NODE_TEXT_COLOR: \"#AAA\",\n        NODE_SUBTEXT_SIZE: 12,\n        NODE_DEFAULT_COLOR: \"#333\",\n        NODE_DEFAULT_BGCOLOR: \"#353535\",\n        NODE_DEFAULT_BOXCOLOR: \"#666\",\n        NODE_DEFAULT_SHAPE: \"box\",\n        NODE_BOX_OUTLINE_COLOR: \"#FFF\",\n        DEFAULT_SHADOW_COLOR: \"rgba(0,0,0,0.5)\",\n        DEFAULT_GROUP_FONT: 24,\n\n        WIDGET_BGCOLOR: \"#222\",\n        WIDGET_OUTLINE_COLOR: \"#666\",\n        WIDGET_TEXT_COLOR: \"#DDD\",\n        WIDGET_SECONDARY_TEXT_COLOR: \"#999\",\n\n        LINK_COLOR: \"#9A9\",\n        EVENT_LINK_COLOR: \"#A86\",\n        CONNECTING_LINK_COLOR: \"#AFA\",\n\n        MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops\n        DEFAULT_POSITION: [100, 100], //default node position\n        VALID_SHAPES: [\"default\", \"box\", \"round\", \"card\"], //,\"circle\"\n\n        //shapes are used for nodes but also for slots\n        BOX_SHAPE: 1,\n        ROUND_SHAPE: 2,\n        CIRCLE_SHAPE: 3,\n        CARD_SHAPE: 4,\n        ARROW_SHAPE: 5,\n        GRID_SHAPE: 6, // intended for slot arrays\n\n        //enums\n        INPUT: 1,\n        OUTPUT: 2,\n\n        EVENT: -1, //for outputs\n        ACTION: -1, //for inputs\n\n        NODE_MODES: [\"Always\", \"On Event\", \"Never\", \"On Trigger\"], // helper, will add \"On Request\" and more in the future\n        NODE_MODES_COLORS:[\"#666\",\"#422\",\"#333\",\"#224\",\"#626\"], // use with node_box_coloured_by_mode\n        ALWAYS: 0,\n        ON_EVENT: 1,\n        NEVER: 2,\n        ON_TRIGGER: 3,\n\n        UP: 1,\n        DOWN: 2,\n        LEFT: 3,\n        RIGHT: 4,\n        CENTER: 5,\n\n        LINK_RENDER_MODES: [\"Straight\", \"Linear\", \"Spline\"], // helper\n        STRAIGHT_LINK: 0,\n        LINEAR_LINK: 1,\n        SPLINE_LINK: 2,\n\n        NORMAL_TITLE: 0,\n        NO_TITLE: 1,\n        TRANSPARENT_TITLE: 2,\n        AUTOHIDE_TITLE: 3,\n        VERTICAL_LAYOUT: \"vertical\", // arrange nodes vertically\n\n        proxy: null, //used to redirect calls\n        node_images_path: \"\",\n\n        debug: false,\n        catch_exceptions: true,\n        throw_errors: true,\n        allow_scripts: false, //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits\n        use_deferred_actions: true, //executes actions during the graph execution flow\n        registered_node_types: {}, //nodetypes by string\n        node_types_by_file_extension: {}, //used for dropping files in the canvas\n        Nodes: {}, //node types by classname\n\t\tGlobals: {}, //used to store vars between graphs\n\n        searchbox_extras: {}, //used to add extra features to the search box\n        auto_sort_node_types: false, // [true!] If set to true, will automatically sort node types / categories in the context menus\n\t\t\n\t\tnode_box_coloured_when_on: false, // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback\n        node_box_coloured_by_mode: false, // [true!] nodebox based on node mode, visual feedback\n        \n        dialog_close_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false\n        dialog_close_on_mouse_leave_delay: 500,\n        \n        shift_click_do_break_link_from: false, // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys\n        click_do_break_link_to: false, // [false!]prefer false, way too easy to break links\n        \n        search_hide_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false\n        search_filter_enabled: false, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out]\n        search_show_all_on_open: true, // [true!] opens the results list when opening the search widget\n        \n        auto_load_slot_types: false, // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out]\n        \n\t\t// set these values if not using auto_load_slot_types\n        registered_slot_in_types: {}, // slot types for nodeclass\n        registered_slot_out_types: {}, // slot types for nodeclass\n        slot_types_in: [], // slot types IN\n        slot_types_out: [], // slot types OUT\n        slot_types_default_in: [], // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search\n\t\tslot_types_default_out: [], // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search\n\t\t\n\t\talt_drag_do_clone_nodes: false, // [true!] very handy, ALT click to clone and drag the new node\n\n\t\tdo_add_triggers_slots: false, // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this\n\t\t\n\t\tallow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentially, one by one\n\n\t\tmiddle_click_slot_add_default_node: false, //[true!] allows to create and connect a ndoe clicking with the third button (wheel)\n\t\t\n\t\trelease_link_on_empty_shows_menu: false, //[true!] dragging a link to empty space will open a menu, add from list, search or defaults\n\t\t\n        pointerevents_method: \"mouse\", // \"mouse\"|\"pointer\" use mouse for retrocompatibility issues? (none found @ now)\n        // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary)\n\n        ctrl_shift_v_paste_connect_unselected_outputs: false, //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes\n\n        // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers.\n        // use this if you must have node IDs that are unique across all graphs and subgraphs.\n        use_uuids: false,\n\n        /**\n         * Register a node class so it can be listed when the user wants to create a new one\n         * @method registerNodeType\n         * @param {String} type name of the node and path\n         * @param {Class} base_class class containing the structure of a node\n         */\n\n        registerNodeType: function(type, base_class) {\n            if (!base_class.prototype) {\n                throw \"Cannot register a simple object, it must be a class with a prototype\";\n            }\n            base_class.type = type;\n\n            if (LiteGraph.debug) {\n                console.log(\"Node registered: \" + type);\n            }\n\n            const classname = base_class.name;\n\n            const pos = type.lastIndexOf(\"/\");\n            base_class.category = type.substring(0, pos);\n\n            if (!base_class.title) {\n                base_class.title = classname;\n            }\n\n            //extend class\n            for (var i in LGraphNode.prototype) {\n                if (!base_class.prototype[i]) {\n                    base_class.prototype[i] = LGraphNode.prototype[i];\n                }\n            }\n\n            const prev = this.registered_node_types[type];\n            if(prev) {\n                console.log(\"replacing node type: \" + type);\n            }\n            if( !Object.prototype.hasOwnProperty.call( base_class.prototype, \"shape\") ) {\n                Object.defineProperty(base_class.prototype, \"shape\", {\n                    set: function(v) {\n                        switch (v) {\n                            case \"default\":\n                                delete this._shape;\n                                break;\n                            case \"box\":\n                                this._shape = LiteGraph.BOX_SHAPE;\n                                break;\n                            case \"round\":\n                                this._shape = LiteGraph.ROUND_SHAPE;\n                                break;\n                            case \"circle\":\n                                this._shape = LiteGraph.CIRCLE_SHAPE;\n                                break;\n                            case \"card\":\n                                this._shape = LiteGraph.CARD_SHAPE;\n                                break;\n                            default:\n                                this._shape = v;\n                        }\n                    },\n                    get: function() {\n                        return this._shape;\n                    },\n                    enumerable: true,\n                    configurable: true\n                });\n                \n\n                //used to know which nodes to create when dragging files to the canvas\n                if (base_class.supported_extensions) {\n                    for (let i in base_class.supported_extensions) {\n                        const ext = base_class.supported_extensions[i];\n                        if(ext && ext.constructor === String) {\n                            this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class;\n                        }\n                    }\n                }\n            }\n\n            this.registered_node_types[type] = base_class;\n            if (base_class.constructor.name) {\n                this.Nodes[classname] = base_class;\n            }\n            if (LiteGraph.onNodeTypeRegistered) {\n                LiteGraph.onNodeTypeRegistered(type, base_class);\n            }\n            if (prev && LiteGraph.onNodeTypeReplaced) {\n                LiteGraph.onNodeTypeReplaced(type, base_class, prev);\n            }\n\n            //warnings\n            if (base_class.prototype.onPropertyChange) {\n                console.warn(\n                    \"LiteGraph node class \" +\n                        type +\n                        \" has onPropertyChange method, it must be called onPropertyChanged with d at the end\"\n                );\n            }\n            \n            // TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types\n            if (this.auto_load_slot_types) {\n                new base_class(base_class.title || \"tmpnode\");\n            }\n        },\n\n        /**\n         * removes a node type from the system\n         * @method unregisterNodeType\n         * @param {String|Object} type name of the node or the node constructor itself\n         */\n        unregisterNodeType: function(type) {\n            const base_class =\n                type.constructor === String\n                    ? this.registered_node_types[type]\n                    : type;\n            if (!base_class) {\n                throw \"node type not found: \" + type;\n            }\n            delete this.registered_node_types[base_class.type];\n            if (base_class.constructor.name) {\n                delete this.Nodes[base_class.constructor.name];\n            }\n        },\n\n        /**\n        * Save a slot type and his node\n        * @method registerSlotType\n        * @param {String|Object} type name of the node or the node constructor itself\n        * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, ..\n        */\n        registerNodeAndSlotType: function(type, slot_type, out){\n            out = out || false;\n            const base_class =\n                type.constructor === String &&\n                this.registered_node_types[type] !== \"anonymous\"\n                    ? this.registered_node_types[type]\n                    : type;\n\n            const class_type = base_class.constructor.type;\n\n            let allTypes = [];\n            if (typeof slot_type === \"string\") {\n                allTypes = slot_type.split(\",\");\n            } else if (slot_type == this.EVENT || slot_type == this.ACTION) {\n                allTypes = [\"_event_\"];\n            } else {\n                allTypes = [\"*\"];\n            }\n\n            for (let i = 0; i < allTypes.length; ++i) {\n                let slotType = allTypes[i];\n                if (slotType === \"\") {\n                    slotType = \"*\";\n                }\n                const registerTo = out\n                    ? \"registered_slot_out_types\"\n                    : \"registered_slot_in_types\";\n                if (this[registerTo][slotType] === undefined) {\n                    this[registerTo][slotType] = { nodes: [] };\n                }\n                if (!this[registerTo][slotType].nodes.includes(class_type)) {\n                    this[registerTo][slotType].nodes.push(class_type);\n                }\n\n                // check if is a new type\n                if (!out) {\n                    if (!this.slot_types_in.includes(slotType.toLowerCase())) {\n                        this.slot_types_in.push(slotType.toLowerCase());\n                        this.slot_types_in.sort();\n                    }\n                } else {\n                    if (!this.slot_types_out.includes(slotType.toLowerCase())) {\n                        this.slot_types_out.push(slotType.toLowerCase());\n                        this.slot_types_out.sort();\n                    }\n                }\n            }\n        },\n        \n        /**\n         * Create a new nodetype by passing an object with some properties\n         * like onCreate, inputs:Array, outputs:Array, properties, onExecute\n         * @method buildNodeClassFromObject\n         * @param {String} name node name with namespace (p.e.: 'math/sum')\n         * @param {Object} object methods expected onCreate, inputs, outputs, properties, onExecute\n         */\n         buildNodeClassFromObject: function(\n            name,\n            object\n        ) {\n            var ctor_code = \"\";\n            if(object.inputs)\n            for(var i=0; i < object.inputs.length; ++i)\n            {\n                var _name = object.inputs[i][0];\n                var _type = object.inputs[i][1];\n                if(_type && _type.constructor === String)\n                    _type = '\"'+_type+'\"';\n                ctor_code += \"this.addInput('\"+_name+\"',\"+_type+\");\\n\";\n            }\n            if(object.outputs)\n            for(var i=0; i < object.outputs.length; ++i)\n            {\n                var _name = object.outputs[i][0];\n                var _type = object.outputs[i][1];\n                if(_type && _type.constructor === String)\n                    _type = '\"'+_type+'\"';\n                ctor_code += \"this.addOutput('\"+_name+\"',\"+_type+\");\\n\";\n            }\n            if(object.properties)\n            for(var i in object.properties)\n            {\n                var prop = object.properties[i];\n                if(prop && prop.constructor === String)\n                    prop = '\"'+prop+'\"';\n                ctor_code += \"this.addProperty('\"+i+\"',\"+prop+\");\\n\";\n            }\n            ctor_code += \"if(this.onCreate)this.onCreate()\";\n            var classobj = Function(ctor_code);\n            for(var i in object)\n                if(i!=\"inputs\" && i!=\"outputs\" && i!=\"properties\")\n                    classobj.prototype[i] = object[i];\n            classobj.title = object.title || name.split(\"/\").pop();\n            classobj.desc = object.desc || \"Generated from object\";\n            this.registerNodeType(name, classobj);\n            return classobj;\n        },\n        \n        /**\n         * 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.\n         * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output.\n         * @method wrapFunctionAsNode\n         * @param {String} name node name with namespace (p.e.: 'math/sum')\n         * @param {Function} func\n         * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type\n         * @param {String} return_type [optional] string with the return type, otherwise it will be generic\n         * @param {Object} properties [optional] properties to be configurable\n         */\n        wrapFunctionAsNode: function(\n            name,\n            func,\n            param_types,\n            return_type,\n            properties\n        ) {\n            var params = Array(func.length);\n            var code = \"\";\n            if(param_types !== null) //null means no inputs\n            {\n                var names = LiteGraph.getParameterNames(func);\n                for (var i = 0; i < names.length; ++i) {\n                    var type = 0;\n                    if(param_types)\n                    {\n                        //type = param_types[i] != null ? \"'\" + param_types[i] + \"'\" : \"0\";\n                        if( param_types[i] != null && param_types[i].constructor === String )\n                            type = \"'\" + param_types[i] + \"'\" ;\n                        else if( param_types[i] != null )\n                            type = param_types[i];\n                    } \n                    code +=\n                        \"this.addInput('\" +\n                        names[i] +\n                        \"',\" +\n                        type +\n                        \");\\n\";\n                }\n            }\n            if(return_type !== null) //null means no output\n            code +=\n                \"this.addOutput('out',\" +\n                (return_type != null ? (return_type.constructor === String ? \"'\" + return_type + \"'\" : return_type) : 0) +\n                \");\\n\";\n            if (properties) {\n                code +=\n                    \"this.properties = \" + JSON.stringify(properties) + \";\\n\";\n            }\n            var classobj = Function(code);\n            classobj.title = name.split(\"/\").pop();\n            classobj.desc = \"Generated from \" + func.name;\n            classobj.prototype.onExecute = function onExecute() {\n                for (var i = 0; i < params.length; ++i) {\n                    params[i] = this.getInputData(i);\n                }\n                var r = func.apply(this, params);\n                this.setOutputData(0, r);\n            };\n            this.registerNodeType(name, classobj);\n            return classobj;\n        },\n\n        /**\n         * Removes all previously registered node's types\n         */\n        clearRegisteredTypes: function() {\n            this.registered_node_types = {};\n            this.node_types_by_file_extension = {};\n            this.Nodes = {};\n            this.searchbox_extras = {};\n        },\n\n        /**\n         * Adds this method to all nodetypes, existing and to be created\n         * (You can add it to LGraphNode.prototype but then existing node types wont have it)\n         * @method addNodeMethod\n         * @param {Function} func\n         */\n        addNodeMethod: function(name, func) {\n            LGraphNode.prototype[name] = func;\n            for (var i in this.registered_node_types) {\n                var type = this.registered_node_types[i];\n                if (type.prototype[name]) {\n                    type.prototype[\"_\" + name] = type.prototype[name];\n                } //keep old in case of replacing\n                type.prototype[name] = func;\n            }\n        },\n\n        /**\n         * Create a node of a given type with a name. The node is not attached to any graph yet.\n         * @method createNode\n         * @param {String} type full name of the node class. p.e. \"math/sin\"\n         * @param {String} name a name to distinguish from other nodes\n         * @param {Object} options to set options\n         */\n\n        createNode: function(type, title, options) {\n            var base_class = this.registered_node_types[type];\n            if (!base_class) {\n                if (LiteGraph.debug) {\n                    console.log(\n                        'GraphNode type \"' + type + '\" not registered.'\n                    );\n                }\n                return null;\n            }\n\n            var prototype = base_class.prototype || base_class;\n\n            title = title || base_class.title || type;\n\n            var node = null;\n\n            if (LiteGraph.catch_exceptions) {\n                try {\n                    node = new base_class(title);\n                } catch (err) {\n                    console.error(err);\n                    return null;\n                }\n            } else {\n                node = new base_class(title);\n            }\n\n            node.type = type;\n\n            if (!node.title && title) {\n                node.title = title;\n            }\n            if (!node.properties) {\n                node.properties = {};\n            }\n            if (!node.properties_info) {\n                node.properties_info = [];\n            }\n            if (!node.flags) {\n                node.flags = {};\n            }\n            if (!node.size) {\n                node.size = node.computeSize();\n\t\t\t\t//call onresize?\n            }\n            if (!node.pos) {\n                node.pos = LiteGraph.DEFAULT_POSITION.concat();\n            }\n            if (!node.mode) {\n                node.mode = LiteGraph.ALWAYS;\n            }\n\n            //extra options\n            if (options) {\n                for (var i in options) {\n                    node[i] = options[i];\n                }\n            }\n\n\t\t\t// callback\n            if ( node.onNodeCreated ) {\n                node.onNodeCreated();\n            }\n            \n            return node;\n        },\n\n        /**\n         * Returns a registered node type with a given name\n         * @method getNodeType\n         * @param {String} type full name of the node class. p.e. \"math/sin\"\n         * @return {Class} the node class\n         */\n        getNodeType: function(type) {\n            return this.registered_node_types[type];\n        },\n\n        /**\n         * Returns a list of node types matching one category\n         * @method getNodeType\n         * @param {String} category category name\n         * @return {Array} array with all the node classes\n         */\n\n        getNodeTypesInCategory: function(category, filter) {\n            var r = [];\n            for (var i in this.registered_node_types) {\n                var type = this.registered_node_types[i];\n                if (type.filter != filter) {\n                    continue;\n                }\n\n                if (category == \"\") {\n                    if (type.category == null) {\n                        r.push(type);\n                    }\n                } else if (type.category == category) {\n                    r.push(type);\n                }\n            }\n\n            if (this.auto_sort_node_types) {\n                r.sort(function(a,b){return a.title.localeCompare(b.title)});\n            }\n\n            return r;\n        },\n\n        /**\n         * Returns a list with all the node type categories\n         * @method getNodeTypesCategories\n         * @param {String} filter only nodes with ctor.filter equal can be shown\n         * @return {Array} array with all the names of the categories\n         */\n        getNodeTypesCategories: function( filter ) {\n            var categories = { \"\": 1 };\n            for (var i in this.registered_node_types) {\n\t\t\t\tvar type = this.registered_node_types[i];\n                if ( type.category && !type.skip_list )\n                {\n\t\t\t\t\tif(type.filter != filter)\n\t\t\t\t\t\tcontinue;\n                    categories[type.category] = 1;\n                }\n            }\n            var result = [];\n            for (var i in categories) {\n                result.push(i);\n            }\n            return this.auto_sort_node_types ? result.sort() : result;\n        },\n\n        //debug purposes: reloads all the js scripts that matches a wildcard\n        reloadNodes: function(folder_wildcard) {\n            var tmp = document.getElementsByTagName(\"script\");\n            //weird, this array changes by its own, so we use a copy\n            var script_files = [];\n            for (var i=0; i < tmp.length; i++) {\n                script_files.push(tmp[i]);\n            }\n\n            var docHeadObj = document.getElementsByTagName(\"head\")[0];\n            folder_wildcard = document.location.href + folder_wildcard;\n\n            for (var i=0; i < script_files.length; i++) {\n                var src = script_files[i].src;\n                if (\n                    !src ||\n                    src.substr(0, folder_wildcard.length) != folder_wildcard\n                ) {\n                    continue;\n                }\n\n                try {\n                    if (LiteGraph.debug) {\n                        console.log(\"Reloading: \" + src);\n                    }\n                    var dynamicScript = document.createElement(\"script\");\n                    dynamicScript.type = \"text/javascript\";\n                    dynamicScript.src = src;\n                    docHeadObj.appendChild(dynamicScript);\n                    docHeadObj.removeChild(script_files[i]);\n                } catch (err) {\n                    if (LiteGraph.throw_errors) {\n                        throw err;\n                    }\n                    if (LiteGraph.debug) {\n                        console.log(\"Error while reloading \" + src);\n                    }\n                }\n            }\n\n            if (LiteGraph.debug) {\n                console.log(\"Nodes reloaded\");\n            }\n        },\n\n        //separated just to improve if it doesn't work\n        cloneObject: function(obj, target) {\n            if (obj == null) {\n                return null;\n            }\n            var r = JSON.parse(JSON.stringify(obj));\n            if (!target) {\n                return r;\n            }\n\n            for (var i in r) {\n                target[i] = r[i];\n            }\n            return target;\n        },\n\n        /*\n         * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670\n         */\n        uuidv4: function() {\n            return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,a=>(a^Math.random()*16>>a/4).toString(16));\n        },\n\n        /**\n         * Returns if the types of two slots are compatible (taking into account wildcards, etc)\n         * @method isValidConnection\n         * @param {String} type_a\n         * @param {String} type_b\n         * @return {Boolean} true if they can be connected\n         */\n        isValidConnection: function(type_a, type_b) {\n\t\t\tif (type_a==\"\" || type_a===\"*\") type_a = 0;\n\t\t\tif (type_b==\"\" || type_b===\"*\") type_b = 0;\n            if (\n                !type_a //generic output\n                || !type_b // generic input\n                || type_a == type_b //same type (is valid for triggers)\n                || (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION)\n            ) {\n                return true;\n            }\n\n            // Enforce string type to handle toLowerCase call (-1 number not ok)\n            type_a = String(type_a);\n            type_b = String(type_b);\n            type_a = type_a.toLowerCase();\n            type_b = type_b.toLowerCase();\n\n            // For nodes supporting multiple connection types\n            if (type_a.indexOf(\",\") == -1 && type_b.indexOf(\",\") == -1) {\n                return type_a == type_b;\n            }\n\n            // Check all permutations to see if one is valid\n            var supported_types_a = type_a.split(\",\");\n            var supported_types_b = type_b.split(\",\");\n            for (var i = 0; i < supported_types_a.length; ++i) {\n                for (var j = 0; j < supported_types_b.length; ++j) {\n                    if(this.isValidConnection(supported_types_a[i],supported_types_b[j])){\n\t\t\t\t\t//if (supported_types_a[i] == supported_types_b[j]) {\n                        return true;\n                    }\n                }\n            }\n\n            return false;\n        },\n\n        /**\n         * Register a string in the search box so when the user types it it will recommend this node\n         * @method registerSearchboxExtra\n         * @param {String} node_type the node recommended\n         * @param {String} description text to show next to it\n         * @param {Object} data it could contain info of how the node should be configured\n         * @return {Boolean} true if they can be connected\n         */\n        registerSearchboxExtra: function(node_type, description, data) {\n            this.searchbox_extras[description.toLowerCase()] = {\n                type: node_type,\n                desc: description,\n                data: data\n            };\n        },\n\n        /**\n         * Wrapper to load files (from url using fetch or from file using FileReader)\n         * @method fetchFile\n         * @param {String|File|Blob} url the url of the file (or the file itself)\n         * @param {String} type an string to know how to fetch it: \"text\",\"arraybuffer\",\"json\",\"blob\"\n         * @param {Function} on_complete callback(data)\n         * @param {Function} on_error in case of an error\n         * @return {FileReader|Promise} returns the object used to \n         */\n\t\tfetchFile: function( url, type, on_complete, on_error ) {\n\t\t\tvar that = this;\n\t\t\tif(!url)\n\t\t\t\treturn null;\n\n\t\t\ttype = type || \"text\";\n\t\t\tif( url.constructor === String )\n\t\t\t{\n\t\t\t\tif (url.substr(0, 4) == \"http\" && LiteGraph.proxy) {\n\t\t\t\t\turl = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\n\t\t\t\t}\n\t\t\t\treturn fetch(url)\n\t\t\t\t.then(function(response) {\n\t\t\t\t\tif(!response.ok)\n\t\t\t\t\t\t throw new Error(\"File not found\"); //it will be catch below\n\t\t\t\t\tif(type == \"arraybuffer\")\n\t\t\t\t\t\treturn response.arrayBuffer();\n\t\t\t\t\telse if(type == \"text\" || type == \"string\")\n\t\t\t\t\t\treturn response.text();\n\t\t\t\t\telse if(type == \"json\")\n\t\t\t\t\t\treturn response.json();\n\t\t\t\t\telse if(type == \"blob\")\n\t\t\t\t\t\treturn response.blob();\n\t\t\t\t})\n\t\t\t\t.then(function(data) {\n\t\t\t\t\tif(on_complete)\n\t\t\t\t\t\ton_complete(data);\n\t\t\t\t})\n\t\t\t\t.catch(function(error) {\n\t\t\t\t\tconsole.error(\"error fetching file:\",url);\n\t\t\t\t\tif(on_error)\n\t\t\t\t\t\ton_error(error);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if( url.constructor === File || url.constructor === Blob)\n\t\t\t{\n\t\t\t\tvar reader = new FileReader();\n\t\t\t\treader.onload = function(e)\n\t\t\t\t{\n\t\t\t\t\tvar v = e.target.result;\n\t\t\t\t\tif( type == \"json\" )\n\t\t\t\t\t\tv = JSON.parse(v);\n\t\t\t\t\tif(on_complete)\n\t\t\t\t\t\ton_complete(v);\n\t\t\t\t}\n\t\t\t\tif(type == \"arraybuffer\")\n\t\t\t\t\treturn reader.readAsArrayBuffer(url);\n\t\t\t\telse if(type == \"text\" || type == \"json\")\n\t\t\t\t\treturn reader.readAsText(url);\n\t\t\t\telse if(type == \"blob\")\n\t\t\t\t\treturn reader.readAsBinaryString(url);\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n    });\n\n    //timer that works everywhere\n    if (typeof performance != \"undefined\") {\n        LiteGraph.getTime = performance.now.bind(performance);\n    } else if (typeof Date != \"undefined\" && Date.now) {\n        LiteGraph.getTime = Date.now.bind(Date);\n    } else if (typeof process != \"undefined\") {\n        LiteGraph.getTime = function() {\n            var t = process.hrtime();\n            return t[0] * 0.001 + t[1] * 1e-6;\n        };\n    } else {\n        LiteGraph.getTime = function getTime() {\n            return new Date().getTime();\n        };\n    }\n\n    //*********************************************************************************\n    // LGraph CLASS\n    //*********************************************************************************\n\n    /**\n     * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.\n\t * supported callbacks:\n\t\t+ onNodeAdded: when a new node is added to the graph\n\t\t+ onNodeRemoved: when a node inside this graph is removed\n\t\t+ onNodeConnectionChange: some connection has changed in the graph (connected or disconnected)\n     *\n     * @class LGraph\n     * @constructor\n     * @param {Object} o data from previous serialization [optional]\n     */\n\n    function LGraph(o) {\n        if (LiteGraph.debug) {\n            console.log(\"Graph created\");\n        }\n        this.list_of_graphcanvas = null;\n        this.clear();\n\n        if (o) {\n            this.configure(o);\n        }\n    }\n\n    global.LGraph = LiteGraph.LGraph = LGraph;\n\n    //default supported types\n    LGraph.supported_types = [\"number\", \"string\", \"boolean\"];\n\n    //used to know which types of connections support this graph (some graphs do not allow certain types)\n    LGraph.prototype.getSupportedTypes = function() {\n        return this.supported_types || LGraph.supported_types;\n    };\n\n    LGraph.STATUS_STOPPED = 1;\n    LGraph.STATUS_RUNNING = 2;\n\n    /**\n     * Removes all nodes from this graph\n     * @method clear\n     */\n\n    LGraph.prototype.clear = function() {\n        this.stop();\n        this.status = LGraph.STATUS_STOPPED;\n\n        this.last_node_id = 0;\n        this.last_link_id = 0;\n\n        this._version = -1; //used to detect changes\n\n        //safe clear\n        if (this._nodes) {\n            for (var i = 0; i < this._nodes.length; ++i) {\n                var node = this._nodes[i];\n                if (node.onRemoved) {\n                    node.onRemoved();\n                }\n            }\n        }\n\n        //nodes\n        this._nodes = [];\n        this._nodes_by_id = {};\n        this._nodes_in_order = []; //nodes sorted in execution order\n        this._nodes_executable = null; //nodes that contain onExecute sorted in execution order\n\n        //other scene stuff\n        this._groups = [];\n\n        //links\n        this.links = {}; //container with all the links\n\n        //iterations\n        this.iteration = 0;\n\n        //custom data\n        this.config = {};\n\t\tthis.vars = {};\n\t\tthis.extra = {}; //to store custom data\n\n        //timing\n        this.globaltime = 0;\n        this.runningtime = 0;\n        this.fixedtime = 0;\n        this.fixedtime_lapse = 0.01;\n        this.elapsed_time = 0.01;\n        this.last_update_time = 0;\n        this.starttime = 0;\n\n        this.catch_errors = true;\n\n        this.nodes_executing = [];\n        this.nodes_actioning = [];\n        this.nodes_executedAction = [];\n        \n        //subgraph_data\n        this.inputs = {};\n        this.outputs = {};\n\n        //notify canvas to redraw\n        this.change();\n\n        this.sendActionToCanvas(\"clear\");\n    };\n\n    /**\n     * Attach Canvas to this graph\n     * @method attachCanvas\n     * @param {GraphCanvas} graph_canvas\n     */\n\n    LGraph.prototype.attachCanvas = function(graphcanvas) {\n        if (graphcanvas.constructor != LGraphCanvas) {\n            throw \"attachCanvas expects a LGraphCanvas instance\";\n        }\n        if (graphcanvas.graph && graphcanvas.graph != this) {\n            graphcanvas.graph.detachCanvas(graphcanvas);\n        }\n\n        graphcanvas.graph = this;\n\n        if (!this.list_of_graphcanvas) {\n            this.list_of_graphcanvas = [];\n        }\n        this.list_of_graphcanvas.push(graphcanvas);\n    };\n\n    /**\n     * Detach Canvas from this graph\n     * @method detachCanvas\n     * @param {GraphCanvas} graph_canvas\n     */\n    LGraph.prototype.detachCanvas = function(graphcanvas) {\n        if (!this.list_of_graphcanvas) {\n            return;\n        }\n\n        var pos = this.list_of_graphcanvas.indexOf(graphcanvas);\n        if (pos == -1) {\n            return;\n        }\n        graphcanvas.graph = null;\n        this.list_of_graphcanvas.splice(pos, 1);\n    };\n\n    /**\n     * Starts running this graph every interval milliseconds.\n     * @method start\n     * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate\n     */\n\n    LGraph.prototype.start = function(interval) {\n        if (this.status == LGraph.STATUS_RUNNING) {\n            return;\n        }\n        this.status = LGraph.STATUS_RUNNING;\n\n        if (this.onPlayEvent) {\n            this.onPlayEvent();\n        }\n\n        this.sendEventToAllNodes(\"onStart\");\n\n        //launch\n        this.starttime = LiteGraph.getTime();\n        this.last_update_time = this.starttime;\n        interval = interval || 0;\n        var that = this;\n\n\t\t//execute once per frame\n        if ( interval == 0 && typeof window != \"undefined\" && window.requestAnimationFrame ) {\n            function on_frame() {\n                if (that.execution_timer_id != -1) {\n                    return;\n                }\n                window.requestAnimationFrame(on_frame);\n\t\t\t\tif(that.onBeforeStep)\n\t\t\t\t\tthat.onBeforeStep();\n                that.runStep(1, !that.catch_errors);\n\t\t\t\tif(that.onAfterStep)\n\t\t\t\t\tthat.onAfterStep();\n            }\n            this.execution_timer_id = -1;\n            on_frame();\n        } else { //execute every 'interval' ms\n            this.execution_timer_id = setInterval(function() {\n                //execute\n\t\t\t\tif(that.onBeforeStep)\n\t\t\t\t\tthat.onBeforeStep();\n                that.runStep(1, !that.catch_errors);\n\t\t\t\tif(that.onAfterStep)\n\t\t\t\t\tthat.onAfterStep();\n            }, interval);\n        }\n    };\n\n    /**\n     * Stops the execution loop of the graph\n     * @method stop execution\n     */\n\n    LGraph.prototype.stop = function() {\n        if (this.status == LGraph.STATUS_STOPPED) {\n            return;\n        }\n\n        this.status = LGraph.STATUS_STOPPED;\n\n        if (this.onStopEvent) {\n            this.onStopEvent();\n        }\n\n        if (this.execution_timer_id != null) {\n            if (this.execution_timer_id != -1) {\n                clearInterval(this.execution_timer_id);\n            }\n            this.execution_timer_id = null;\n        }\n\n        this.sendEventToAllNodes(\"onStop\");\n    };\n\n    /**\n     * Run N steps (cycles) of the graph\n     * @method runStep\n     * @param {number} num number of steps to run, default is 1\n     * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors \n     * @param {number} limit max number of nodes to execute (used to execute from start to a node)\n     */\n\n    LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) {\n        num = num || 1;\n\n        var start = LiteGraph.getTime();\n        this.globaltime = 0.001 * (start - this.starttime);\n\n        //not optimal: executes possible pending actions in node, problem is it is not optimized\n        //it is done here as if it was done in the later loop it wont be called in the node missed the onExecute\n        \n        //from now on it will iterate only on executable nodes which is faster\n        var nodes = this._nodes_executable\n            ? this._nodes_executable\n            : this._nodes;\n        if (!nodes) {\n            return;\n        }\n\n\t\tlimit = limit || nodes.length;\n\n        if (do_not_catch_errors) {\n            //iterations\n            for (var i = 0; i < num; i++) {\n                for (var j = 0; j < limit; ++j) {\n                    var node = nodes[j];\n                    if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length)\n                        node.executePendingActions();\n                    if (node.mode == LiteGraph.ALWAYS && node.onExecute) {\n                        //wrap node.onExecute();\n\t\t\t\t\t\tnode.doExecute();\n                    }\n                }\n\n                this.fixedtime += this.fixedtime_lapse;\n                if (this.onExecuteStep) {\n                    this.onExecuteStep();\n                }\n            }\n\n            if (this.onAfterExecute) {\n                this.onAfterExecute();\n            }\n        } else { //catch errors\n            try {\n                //iterations\n                for (var i = 0; i < num; i++) {\n                    for (var j = 0; j < limit; ++j) {\n                        var node = nodes[j];\n                        if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length)\n                            node.executePendingActions();\n                        if (node.mode == LiteGraph.ALWAYS && node.onExecute) {\n                            node.onExecute();\n                        }\n                    }\n\n                    this.fixedtime += this.fixedtime_lapse;\n                    if (this.onExecuteStep) {\n                        this.onExecuteStep();\n                    }\n                }\n\n                if (this.onAfterExecute) {\n                    this.onAfterExecute();\n                }\n                this.errors_in_execution = false;\n            } catch (err) {\n                this.errors_in_execution = true;\n                if (LiteGraph.throw_errors) {\n                    throw err;\n                }\n                if (LiteGraph.debug) {\n                    console.log(\"Error during execution: \" + err);\n                }\n                this.stop();\n            }\n        }\n\n        var now = LiteGraph.getTime();\n        var elapsed = now - start;\n        if (elapsed == 0) {\n            elapsed = 1;\n        }\n        this.execution_time = 0.001 * elapsed;\n        this.globaltime += 0.001 * elapsed;\n        this.iteration += 1;\n        this.elapsed_time = (now - this.last_update_time) * 0.001;\n        this.last_update_time = now;\n        this.nodes_executing = [];\n        this.nodes_actioning = [];\n        this.nodes_executedAction = [];\n    };\n\n    /**\n     * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than\n     * nodes with only inputs.\n     * @method updateExecutionOrder\n     */\n    LGraph.prototype.updateExecutionOrder = function() {\n        this._nodes_in_order = this.computeExecutionOrder(false);\n        this._nodes_executable = [];\n        for (var i = 0; i < this._nodes_in_order.length; ++i) {\n            if (this._nodes_in_order[i].onExecute) {\n                this._nodes_executable.push(this._nodes_in_order[i]);\n            }\n        }\n    };\n\n    //This is more internal, it computes the executable nodes in order and returns it\n    LGraph.prototype.computeExecutionOrder = function(\n        only_onExecute,\n        set_level\n    ) {\n        var L = [];\n        var S = [];\n        var M = {};\n        var visited_links = {}; //to avoid repeating links\n        var remaining_links = {}; //to a\n\n        //search for the nodes without inputs (starting nodes)\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            var node = this._nodes[i];\n            if (only_onExecute && !node.onExecute) {\n                continue;\n            }\n\n            M[node.id] = node; //add to pending nodes\n\n            var num = 0; //num of input connections\n            if (node.inputs) {\n                for (var j = 0, l2 = node.inputs.length; j < l2; j++) {\n                    if (node.inputs[j] && node.inputs[j].link != null) {\n                        num += 1;\n                    }\n                }\n            }\n\n            if (num == 0) {\n                //is a starting node\n                S.push(node);\n                if (set_level) {\n                    node._level = 1;\n                }\n            } //num of input links\n            else {\n                if (set_level) {\n                    node._level = 0;\n                }\n                remaining_links[node.id] = num;\n            }\n        }\n\n        while (true) {\n            if (S.length == 0) {\n                break;\n            }\n\n            //get an starting node\n            var node = S.shift();\n            L.push(node); //add to ordered list\n            delete M[node.id]; //remove from the pending nodes\n\n            if (!node.outputs) {\n                continue;\n            }\n\n            //for every output\n            for (var i = 0; i < node.outputs.length; i++) {\n                var output = node.outputs[i];\n                //not connected\n                if (\n                    output == null ||\n                    output.links == null ||\n                    output.links.length == 0\n                ) {\n                    continue;\n                }\n\n                //for every connection\n                for (var j = 0; j < output.links.length; j++) {\n                    var link_id = output.links[j];\n                    var link = this.links[link_id];\n                    if (!link) {\n                        continue;\n                    }\n\n                    //already visited link (ignore it)\n                    if (visited_links[link.id]) {\n                        continue;\n                    }\n\n                    var target_node = this.getNodeById(link.target_id);\n                    if (target_node == null) {\n                        visited_links[link.id] = true;\n                        continue;\n                    }\n\n                    if (\n                        set_level &&\n                        (!target_node._level ||\n                            target_node._level <= node._level)\n                    ) {\n                        target_node._level = node._level + 1;\n                    }\n\n                    visited_links[link.id] = true; //mark as visited\n                    remaining_links[target_node.id] -= 1; //reduce the number of links remaining\n                    if (remaining_links[target_node.id] == 0) {\n                        S.push(target_node);\n                    } //if no more links, then add to starters array\n                }\n            }\n        }\n\n        //the remaining ones (loops)\n        for (var i in M) {\n            L.push(M[i]);\n        }\n\n        if (L.length != this._nodes.length && LiteGraph.debug) {\n            console.warn(\"something went wrong, nodes missing\");\n        }\n\n        var l = L.length;\n\n        //save order number in the node\n        for (var i = 0; i < l; ++i) {\n            L[i].order = i;\n        }\n\n        //sort now by priority\n        L = L.sort(function(A, B) {\n            var Ap = A.constructor.priority || A.priority || 0;\n            var Bp = B.constructor.priority || B.priority || 0;\n            if (Ap == Bp) {\n                //if same priority, sort by order\n                return A.order - B.order;\n            }\n            return Ap - Bp; //sort by priority\n        });\n\n        //save order number in the node, again...\n        for (var i = 0; i < l; ++i) {\n            L[i].order = i;\n        }\n\n        return L;\n    };\n\n    /**\n     * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.\n     * It doesn't include the node itself\n     * @method getAncestors\n     * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution\n     */\n    LGraph.prototype.getAncestors = function(node) {\n        var ancestors = [];\n        var pending = [node];\n        var visited = {};\n\n        while (pending.length) {\n            var current = pending.shift();\n            if (!current.inputs) {\n                continue;\n            }\n            if (!visited[current.id] && current != node) {\n                visited[current.id] = true;\n                ancestors.push(current);\n            }\n\n            for (var i = 0; i < current.inputs.length; ++i) {\n                var input = current.getInputNode(i);\n                if (input && ancestors.indexOf(input) == -1) {\n                    pending.push(input);\n                }\n            }\n        }\n\n        ancestors.sort(function(a, b) {\n            return a.order - b.order;\n        });\n        return ancestors;\n    };\n\n    /**\n     * Positions every node in a more readable manner\n     * @method arrange\n     */\n    LGraph.prototype.arrange = function (margin, layout) {\n        margin = margin || 100;\n\n        const nodes = this.computeExecutionOrder(false, true);\n        const columns = [];\n        for (let i = 0; i < nodes.length; ++i) {\n            const node = nodes[i];\n            const col = node._level || 1;\n            if (!columns[col]) {\n                columns[col] = [];\n            }\n            columns[col].push(node);\n        }\n\n        let x = margin;\n\n        for (let i = 0; i < columns.length; ++i) {\n            const column = columns[i];\n            if (!column) {\n                continue;\n            }\n            let max_size = 100;\n            let y = margin + LiteGraph.NODE_TITLE_HEIGHT;\n            for (let j = 0; j < column.length; ++j) {\n                const node = column[j];\n                node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x;\n                node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y;\n                const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0;\n                if (node.size[max_size_index] > max_size) {\n                    max_size = node.size[max_size_index];\n                }\n                const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1;\n                y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT;\n            }\n            x += max_size + margin;\n        }\n\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * Returns the amount of time the graph has been running in milliseconds\n     * @method getTime\n     * @return {number} number of milliseconds the graph has been running\n     */\n    LGraph.prototype.getTime = function() {\n        return this.globaltime;\n    };\n\n    /**\n     * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant\n     * @method getFixedTime\n     * @return {number} number of milliseconds the graph has been running\n     */\n\n    LGraph.prototype.getFixedTime = function() {\n        return this.fixedtime;\n    };\n\n    /**\n     * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct\n     * if the nodes are using graphical actions\n     * @method getElapsedTime\n     * @return {number} number of milliseconds it took the last cycle\n     */\n\n    LGraph.prototype.getElapsedTime = function() {\n        return this.elapsed_time;\n    };\n\n    /**\n     * Sends an event to all the nodes, useful to trigger stuff\n     * @method sendEventToAllNodes\n     * @param {String} eventname the name of the event (function to be called)\n     * @param {Array} params parameters in array format\n     */\n    LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) {\n        mode = mode || LiteGraph.ALWAYS;\n\n        var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes;\n        if (!nodes) {\n            return;\n        }\n\n        for (var j = 0, l = nodes.length; j < l; ++j) {\n            var node = nodes[j];\n\n            if (\n                node.constructor === LiteGraph.Subgraph &&\n                eventname != \"onExecute\"\n            ) {\n                if (node.mode == mode) {\n                    node.sendEventToAllNodes(eventname, params, mode);\n                }\n                continue;\n            }\n\n            if (!node[eventname] || node.mode != mode) {\n                continue;\n            }\n            if (params === undefined) {\n                node[eventname]();\n            } else if (params && params.constructor === Array) {\n                node[eventname].apply(node, params);\n            } else {\n                node[eventname](params);\n            }\n        }\n    };\n\n    LGraph.prototype.sendActionToCanvas = function(action, params) {\n        if (!this.list_of_graphcanvas) {\n            return;\n        }\n\n        for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n            var c = this.list_of_graphcanvas[i];\n            if (c[action]) {\n                c[action].apply(c, params);\n            }\n        }\n    };\n\n    /**\n     * Adds a new node instance to this graph\n     * @method add\n     * @param {LGraphNode} node the instance of the node\n     */\n\n    LGraph.prototype.add = function(node, skip_compute_order) {\n        if (!node) {\n            return;\n        }\n\n        //groups\n        if (node.constructor === LGraphGroup) {\n            this._groups.push(node);\n            this.setDirtyCanvas(true);\n            this.change();\n            node.graph = this;\n            this._version++;\n            return;\n        }\n\n        //nodes\n        if (node.id != -1 && this._nodes_by_id[node.id] != null) {\n            console.warn(\n                \"LiteGraph: there is already a node with this ID, changing it\"\n            );\n            if (LiteGraph.use_uuids) {\n                node.id = LiteGraph.uuidv4();\n            }\n            else {\n                node.id = ++this.last_node_id;\n            }\n        }\n\n        if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) {\n            throw \"LiteGraph: max number of nodes in a graph reached\";\n        }\n\n        //give him an id\n        if (LiteGraph.use_uuids) {\n            if (node.id == null || node.id == -1)\n                node.id = LiteGraph.uuidv4();\n        }\n        else {\n            if (node.id == null || node.id == -1) {\n                node.id = ++this.last_node_id;\n            } else if (this.last_node_id < node.id) {\n                this.last_node_id = node.id;\n            }\n        }\n\n        node.graph = this;\n        this._version++;\n\n        this._nodes.push(node);\n        this._nodes_by_id[node.id] = node;\n\n        if (node.onAdded) {\n            node.onAdded(this);\n        }\n\n        if (this.config.align_to_grid) {\n            node.alignToGrid();\n        }\n\n        if (!skip_compute_order) {\n            this.updateExecutionOrder();\n        }\n\n        if (this.onNodeAdded) {\n            this.onNodeAdded(node);\n        }\n\n        this.setDirtyCanvas(true);\n        this.change();\n\n        return node; //to chain actions\n    };\n\n    /**\n     * Removes a node from the graph\n     * @method remove\n     * @param {LGraphNode} node the instance of the node\n     */\n\n    LGraph.prototype.remove = function(node) {\n        if (node.constructor === LiteGraph.LGraphGroup) {\n            var index = this._groups.indexOf(node);\n            if (index != -1) {\n                this._groups.splice(index, 1);\n            }\n            node.graph = null;\n            this._version++;\n            this.setDirtyCanvas(true, true);\n            this.change();\n            return;\n        }\n\n        if (this._nodes_by_id[node.id] == null) {\n            return;\n        } //not found\n\n        if (node.ignore_remove) {\n            return;\n        } //cannot be removed\n\n\t\tthis.beforeChange(); //sure? - almost sure is wrong\n\n        //disconnect inputs\n        if (node.inputs) {\n            for (var i = 0; i < node.inputs.length; i++) {\n                var slot = node.inputs[i];\n                if (slot.link != null) {\n                    node.disconnectInput(i);\n                }\n            }\n        }\n\n        //disconnect outputs\n        if (node.outputs) {\n            for (var i = 0; i < node.outputs.length; i++) {\n                var slot = node.outputs[i];\n                if (slot.links != null && slot.links.length) {\n                    node.disconnectOutput(i);\n                }\n            }\n        }\n\n        //node.id = -1; //why?\n\n        //callback\n        if (node.onRemoved) {\n            node.onRemoved();\n        }\n\n        node.graph = null;\n        this._version++;\n\n        //remove from canvas render\n        if (this.list_of_graphcanvas) {\n            for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n                var canvas = this.list_of_graphcanvas[i];\n                if (canvas.selected_nodes[node.id]) {\n                    delete canvas.selected_nodes[node.id];\n                }\n                if (canvas.node_dragged == node) {\n                    canvas.node_dragged = null;\n                }\n            }\n        }\n\n        //remove from containers\n        var pos = this._nodes.indexOf(node);\n        if (pos != -1) {\n            this._nodes.splice(pos, 1);\n        }\n        delete this._nodes_by_id[node.id];\n\n        if (this.onNodeRemoved) {\n            this.onNodeRemoved(node);\n        }\n\n\t\t//close panels\n\t\tthis.sendActionToCanvas(\"checkPanels\");\n\n        this.setDirtyCanvas(true, true);\n\t\tthis.afterChange(); //sure? - almost sure is wrong\n        this.change();\n\n        this.updateExecutionOrder();\n    };\n\n    /**\n     * Returns a node by its id.\n     * @method getNodeById\n     * @param {Number} id\n     */\n\n    LGraph.prototype.getNodeById = function(id) {\n        if (id == null) {\n            return null;\n        }\n        return this._nodes_by_id[id];\n    };\n\n    /**\n     * Returns a list of nodes that matches a class\n     * @method findNodesByClass\n     * @param {Class} classObject the class itself (not an string)\n     * @return {Array} a list with all the nodes of this type\n     */\n    LGraph.prototype.findNodesByClass = function(classObject, result) {\n        result = result || [];\n        result.length = 0;\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].constructor === classObject) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns a list of nodes that matches a type\n     * @method findNodesByType\n     * @param {String} type the name of the node type\n     * @return {Array} a list with all the nodes of this type\n     */\n    LGraph.prototype.findNodesByType = function(type, result) {\n        var type = type.toLowerCase();\n        result = result || [];\n        result.length = 0;\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].type.toLowerCase() == type) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns the first node that matches a name in its title\n     * @method findNodeByTitle\n     * @param {String} name the name of the node to search\n     * @return {Node} the node or null\n     */\n    LGraph.prototype.findNodeByTitle = function(title) {\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].title == title) {\n                return this._nodes[i];\n            }\n        }\n        return null;\n    };\n\n    /**\n     * Returns a list of nodes that matches a name\n     * @method findNodesByTitle\n     * @param {String} name the name of the node to search\n     * @return {Array} a list with all the nodes with this name\n     */\n    LGraph.prototype.findNodesByTitle = function(title) {\n        var result = [];\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].title == title) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns the top-most node in this position of the canvas\n     * @method getNodeOnPos\n     * @param {number} x the x coordinate in canvas space\n     * @param {number} y the y coordinate in canvas space\n     * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph\n     * @return {LGraphNode} the node at this position or null\n     */\n    LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) {\n        nodes_list = nodes_list || this._nodes;\n\t\tvar nRet = null;\n        for (var i = nodes_list.length - 1; i >= 0; i--) {\n            var n = nodes_list[i];\n            if (n.isPointInside(x, y, margin)) {\n                // check for lesser interest nodes (TODO check for overlapping, use the top)\n\t\t\t\t/*if (typeof n == \"LGraphGroup\"){\n\t\t\t\t\tnRet = n;\n\t\t\t\t}else{*/\n\t\t\t\t\treturn n;\n\t\t\t\t/*}*/\n            }\n        }\n        return nRet;\n    };\n\n    /**\n     * Returns the top-most group in that position\n     * @method getGroupOnPos\n     * @param {number} x the x coordinate in canvas space\n     * @param {number} y the y coordinate in canvas space\n     * @return {LGraphGroup} the group or null\n     */\n    LGraph.prototype.getGroupOnPos = function(x, y) {\n        for (var i = this._groups.length - 1; i >= 0; i--) {\n            var g = this._groups[i];\n            if (g.isPointInside(x, y, 2, true)) {\n                return g;\n            }\n        }\n        return null;\n    };\n\n    /**\n     * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution\n     * this replaces the ones using the old version with the new version\n     * @method checkNodeTypes\n     */\n    LGraph.prototype.checkNodeTypes = function() {\n        var changes = false;\n        for (var i = 0; i < this._nodes.length; i++) {\n            var node = this._nodes[i];\n            var ctor = LiteGraph.registered_node_types[node.type];\n            if (node.constructor == ctor) {\n                continue;\n            }\n            console.log(\"node being replaced by newer version: \" + node.type);\n            var newnode = LiteGraph.createNode(node.type);\n            changes = true;\n            this._nodes[i] = newnode;\n            newnode.configure(node.serialize());\n            newnode.graph = this;\n            this._nodes_by_id[newnode.id] = newnode;\n            if (node.inputs) {\n                newnode.inputs = node.inputs.concat();\n            }\n            if (node.outputs) {\n                newnode.outputs = node.outputs.concat();\n            }\n        }\n        this.updateExecutionOrder();\n    };\n\n    // ********** GLOBALS *****************\n\n    LGraph.prototype.onAction = function(action, param, options) {\n        this._input_nodes = this.findNodesByClass(\n            LiteGraph.GraphInput,\n            this._input_nodes\n        );\n        for (var i = 0; i < this._input_nodes.length; ++i) {\n            var node = this._input_nodes[i];\n            if (node.properties.name != action) {\n                continue;\n            }\n            //wrap node.onAction(action, param);\n            node.actionDo(action, param, options);\n            break;\n        }\n    };\n\n    LGraph.prototype.trigger = function(action, param) {\n        if (this.onTrigger) {\n            this.onTrigger(action, param);\n        }\n    };\n\n    /**\n     * Tell this graph it has a global graph input of this type\n     * @method addGlobalInput\n     * @param {String} name\n     * @param {String} type\n     * @param {*} value [optional]\n     */\n    LGraph.prototype.addInput = function(name, type, value) {\n        var input = this.inputs[name];\n        if (input) {\n            //already exist\n            return;\n        }\n\n\t\tthis.beforeChange();\n        this.inputs[name] = { name: name, type: type, value: value };\n        this._version++;\n\t\tthis.afterChange();\n\n        if (this.onInputAdded) {\n            this.onInputAdded(name, type);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Assign a data to the global graph input\n     * @method setGlobalInputData\n     * @param {String} name\n     * @param {*} data\n     */\n    LGraph.prototype.setInputData = function(name, data) {\n        var input = this.inputs[name];\n        if (!input) {\n            return;\n        }\n        input.value = data;\n    };\n\n    /**\n     * Returns the current value of a global graph input\n     * @method getInputData\n     * @param {String} name\n     * @return {*} the data\n     */\n    LGraph.prototype.getInputData = function(name) {\n        var input = this.inputs[name];\n        if (!input) {\n            return null;\n        }\n        return input.value;\n    };\n\n    /**\n     * Changes the name of a global graph input\n     * @method renameInput\n     * @param {String} old_name\n     * @param {String} new_name\n     */\n    LGraph.prototype.renameInput = function(old_name, name) {\n        if (name == old_name) {\n            return;\n        }\n\n        if (!this.inputs[old_name]) {\n            return false;\n        }\n\n        if (this.inputs[name]) {\n            console.error(\"there is already one input with that name\");\n            return false;\n        }\n\n        this.inputs[name] = this.inputs[old_name];\n        delete this.inputs[old_name];\n        this._version++;\n\n        if (this.onInputRenamed) {\n            this.onInputRenamed(old_name, name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Changes the type of a global graph input\n     * @method changeInputType\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.changeInputType = function(name, type) {\n        if (!this.inputs[name]) {\n            return false;\n        }\n\n        if (\n            this.inputs[name].type &&\n            String(this.inputs[name].type).toLowerCase() ==\n                String(type).toLowerCase()\n        ) {\n            return;\n        }\n\n        this.inputs[name].type = type;\n        this._version++;\n        if (this.onInputTypeChanged) {\n            this.onInputTypeChanged(name, type);\n        }\n    };\n\n    /**\n     * Removes a global graph input\n     * @method removeInput\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.removeInput = function(name) {\n        if (!this.inputs[name]) {\n            return false;\n        }\n\n        delete this.inputs[name];\n        this._version++;\n\n        if (this.onInputRemoved) {\n            this.onInputRemoved(name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n        return true;\n    };\n\n    /**\n     * Creates a global graph output\n     * @method addOutput\n     * @param {String} name\n     * @param {String} type\n     * @param {*} value\n     */\n    LGraph.prototype.addOutput = function(name, type, value) {\n        this.outputs[name] = { name: name, type: type, value: value };\n        this._version++;\n\n        if (this.onOutputAdded) {\n            this.onOutputAdded(name, type);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Assign a data to the global output\n     * @method setOutputData\n     * @param {String} name\n     * @param {String} value\n     */\n    LGraph.prototype.setOutputData = function(name, value) {\n        var output = this.outputs[name];\n        if (!output) {\n            return;\n        }\n        output.value = value;\n    };\n\n    /**\n     * Returns the current value of a global graph output\n     * @method getOutputData\n     * @param {String} name\n     * @return {*} the data\n     */\n    LGraph.prototype.getOutputData = function(name) {\n        var output = this.outputs[name];\n        if (!output) {\n            return null;\n        }\n        return output.value;\n    };\n\n    /**\n     * Renames a global graph output\n     * @method renameOutput\n     * @param {String} old_name\n     * @param {String} new_name\n     */\n    LGraph.prototype.renameOutput = function(old_name, name) {\n        if (!this.outputs[old_name]) {\n            return false;\n        }\n\n        if (this.outputs[name]) {\n            console.error(\"there is already one output with that name\");\n            return false;\n        }\n\n        this.outputs[name] = this.outputs[old_name];\n        delete this.outputs[old_name];\n        this._version++;\n\n        if (this.onOutputRenamed) {\n            this.onOutputRenamed(old_name, name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Changes the type of a global graph output\n     * @method changeOutputType\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.changeOutputType = function(name, type) {\n        if (!this.outputs[name]) {\n            return false;\n        }\n\n        if (\n            this.outputs[name].type &&\n            String(this.outputs[name].type).toLowerCase() ==\n                String(type).toLowerCase()\n        ) {\n            return;\n        }\n\n        this.outputs[name].type = type;\n        this._version++;\n        if (this.onOutputTypeChanged) {\n            this.onOutputTypeChanged(name, type);\n        }\n    };\n\n    /**\n     * Removes a global graph output\n     * @method removeOutput\n     * @param {String} name\n     */\n    LGraph.prototype.removeOutput = function(name) {\n        if (!this.outputs[name]) {\n            return false;\n        }\n        delete this.outputs[name];\n        this._version++;\n\n        if (this.onOutputRemoved) {\n            this.onOutputRemoved(name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n        return true;\n    };\n\n    LGraph.prototype.triggerInput = function(name, value) {\n        var nodes = this.findNodesByTitle(name);\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].onTrigger(value);\n        }\n    };\n\n    LGraph.prototype.setCallback = function(name, func) {\n        var nodes = this.findNodesByTitle(name);\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].setTrigger(func);\n        }\n    };\n\n\t//used for undo, called before any change is made to the graph\n    LGraph.prototype.beforeChange = function(info) {\n        if (this.onBeforeChange) {\n            this.onBeforeChange(this,info);\n        }\n        this.sendActionToCanvas(\"onBeforeChange\", this);\n    };\n\n\t//used to resend actions, called after any change is made to the graph\n    LGraph.prototype.afterChange = function(info) {\n        if (this.onAfterChange) {\n            this.onAfterChange(this,info);\n        }\n        this.sendActionToCanvas(\"onAfterChange\", this);\n    };\n\n    LGraph.prototype.connectionChange = function(node, link_info) {\n        this.updateExecutionOrder();\n        if (this.onConnectionChange) {\n            this.onConnectionChange(node);\n        }\n        this._version++;\n        this.sendActionToCanvas(\"onConnectionChange\");\n    };\n\n    /**\n     * returns if the graph is in live mode\n     * @method isLive\n     */\n\n    LGraph.prototype.isLive = function() {\n        if (!this.list_of_graphcanvas) {\n            return false;\n        }\n\n        for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n            var c = this.list_of_graphcanvas[i];\n            if (c.live_mode) {\n                return true;\n            }\n        }\n        return false;\n    };\n\n    /**\n     * clears the triggered slot animation in all links (stop visual animation)\n     * @method clearTriggeredSlots\n     */\n    LGraph.prototype.clearTriggeredSlots = function() {\n        for (var i in this.links) {\n            var link_info = this.links[i];\n            if (!link_info) {\n                continue;\n            }\n            if (link_info._last_time) {\n                link_info._last_time = 0;\n            }\n        }\n    };\n\n    /* Called when something visually changed (not the graph!) */\n    LGraph.prototype.change = function() {\n        if (LiteGraph.debug) {\n            console.log(\"Graph changed\");\n        }\n        this.sendActionToCanvas(\"setDirty\", [true, true]);\n        if (this.on_change) {\n            this.on_change(this);\n        }\n    };\n\n    LGraph.prototype.setDirtyCanvas = function(fg, bg) {\n        this.sendActionToCanvas(\"setDirty\", [fg, bg]);\n    };\n\n    /**\n     * Destroys a link\n     * @method removeLink\n     * @param {Number} link_id\n     */\n    LGraph.prototype.removeLink = function(link_id) {\n        var link = this.links[link_id];\n        if (!link) {\n            return;\n        }\n        var node = this.getNodeById(link.target_id);\n        if (node) {\n            node.disconnectInput(link.target_slot);\n        }\n    };\n\n    //save and recover app state ***************************************\n    /**\n     * Creates a Object containing all the info about this graph, it can be serialized\n     * @method serialize\n     * @return {Object} value of the node\n     */\n    LGraph.prototype.serialize = function() {\n        var nodes_info = [];\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            nodes_info.push(this._nodes[i].serialize());\n        }\n\n        //pack link info into a non-verbose format\n        var links = [];\n        for (var i in this.links) {\n            //links is an OBJECT\n            var link = this.links[i];\n            if (!link.serialize) {\n                //weird bug I havent solved yet\n                console.warn(\n                    \"weird LLink bug, link info is not a LLink but a regular object\"\n                );\n                var link2 = new LLink();\n                for (var j in link) { \n                    link2[j] = link[j];\n                }\n                this.links[i] = link2;\n                link = link2;\n            }\n\n            links.push(link.serialize());\n        }\n\n        var groups_info = [];\n        for (var i = 0; i < this._groups.length; ++i) {\n            groups_info.push(this._groups[i].serialize());\n        }\n\n        var data = {\n            last_node_id: this.last_node_id,\n            last_link_id: this.last_link_id,\n            nodes: nodes_info,\n            links: links,\n            groups: groups_info,\n            config: this.config,\n\t\t\textra: this.extra,\n            version: LiteGraph.VERSION\n        };\n\n\t\tif(this.onSerialize)\n\t\t\tthis.onSerialize(data);\n\n        return data;\n    };\n\n    /**\n     * Configure a graph from a JSON string\n     * @method configure\n     * @param {String} str configure a graph from a JSON string\n     * @param {Boolean} returns if there was any error parsing\n     */\n    LGraph.prototype.configure = function(data, keep_old) {\n        if (!data) {\n            return;\n        }\n\n        if (!keep_old) {\n            this.clear();\n        }\n\n        var nodes = data.nodes;\n\n        //decode links info (they are very verbose)\n        if (data.links && data.links.constructor === Array) {\n            var links = [];\n            for (var i = 0; i < data.links.length; ++i) {\n                var link_data = data.links[i];\n\t\t\t\tif(!link_data) //weird bug\n\t\t\t\t{\n\t\t\t\t\tconsole.warn(\"serialized graph link data contains errors, skipping.\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n                var link = new LLink();\n                link.configure(link_data);\n                links[link.id] = link;\n            }\n            data.links = links;\n        }\n\n        //copy all stored fields\n        for (var i in data) {\n\t\t\tif(i == \"nodes\" || i == \"groups\" ) //links must be accepted\n\t\t\t\tcontinue;\n            this[i] = data[i];\n        }\n\n        var error = false;\n\n        //create nodes\n        this._nodes = [];\n        if (nodes) {\n            for (var i = 0, l = nodes.length; i < l; ++i) {\n                var n_info = nodes[i]; //stored info\n                var node = LiteGraph.createNode(n_info.type, n_info.title);\n                if (!node) {\n                    if (LiteGraph.debug) {\n                        console.log(\n                            \"Node not found or has errors: \" + n_info.type\n                        );\n                    }\n\n                    //in case of error we create a replacement node to avoid losing info\n                    node = new LGraphNode();\n                    node.last_serialization = n_info;\n                    node.has_errors = true;\n                    error = true;\n                    //continue;\n                }\n\n                node.id = n_info.id; //id it or it will create a new id\n                this.add(node, true); //add before configure, otherwise configure cannot create links\n            }\n\n            //configure nodes afterwards so they can reach each other\n            for (var i = 0, l = nodes.length; i < l; ++i) {\n                var n_info = nodes[i];\n                var node = this.getNodeById(n_info.id);\n                if (node) {\n                    node.configure(n_info);\n                }\n            }\n        }\n\n        //groups\n        this._groups.length = 0;\n        if (data.groups) {\n            for (var i = 0; i < data.groups.length; ++i) {\n                var group = new LiteGraph.LGraphGroup();\n                group.configure(data.groups[i]);\n                this.add(group);\n            }\n        }\n\n        this.updateExecutionOrder();\n\n\t\tthis.extra = data.extra || {};\n\n\t\tif(this.onConfigure)\n\t\t\tthis.onConfigure(data);\n\n        this._version++;\n        this.setDirtyCanvas(true, true);\n        return error;\n    };\n\n    LGraph.prototype.load = function(url, callback) {\n        var that = this;\n\n\t\t//from file\n\t\tif(url.constructor === File || url.constructor === Blob)\n\t\t{\n\t\t\tvar reader = new FileReader();\n\t\t\treader.addEventListener('load', function(event) {\n\t\t\t\tvar data = JSON.parse(event.target.result);\n\t\t\t\tthat.configure(data);\n\t\t\t\tif(callback)\n\t\t\t\t\tcallback();\n\t\t\t});\n\t\t\t\n\t\t\treader.readAsText(url);\n\t\t\treturn;\n\t\t}\n\n\t\t//is a string, then an URL\n        var req = new XMLHttpRequest();\n        req.open(\"GET\", url, true);\n        req.send(null);\n        req.onload = function(oEvent) {\n            if (req.status !== 200) {\n                console.error(\"Error loading graph:\", req.status, req.response);\n                return;\n            }\n            var data = JSON.parse( req.response );\n            that.configure(data);\n\t\t\tif(callback)\n\t\t\t\tcallback();\n        };\n        req.onerror = function(err) {\n            console.error(\"Error loading graph:\", err);\n        };\n    };\n\n    LGraph.prototype.onNodeTrace = function(node, msg, color) {\n        //TODO\n    };\n\n    //this is the class in charge of storing link information\n    function LLink(id, type, origin_id, origin_slot, target_id, target_slot) {\n        this.id = id;\n        this.type = type;\n        this.origin_id = origin_id;\n        this.origin_slot = origin_slot;\n        this.target_id = target_id;\n        this.target_slot = target_slot;\n\n        this._data = null;\n        this._pos = new Float32Array(2); //center\n    }\n\n    LLink.prototype.configure = function(o) {\n        if (o.constructor === Array) {\n            this.id = o[0];\n            this.origin_id = o[1];\n            this.origin_slot = o[2];\n            this.target_id = o[3];\n            this.target_slot = o[4];\n            this.type = o[5];\n        } else {\n            this.id = o.id;\n            this.type = o.type;\n            this.origin_id = o.origin_id;\n            this.origin_slot = o.origin_slot;\n            this.target_id = o.target_id;\n            this.target_slot = o.target_slot;\n        }\n    };\n\n    LLink.prototype.serialize = function() {\n        return [\n            this.id,\n            this.origin_id,\n            this.origin_slot,\n            this.target_id,\n            this.target_slot,\n            this.type\n        ];\n    };\n\n    LiteGraph.LLink = LLink;\n\n    // *************************************************************\n    //   Node CLASS                                          *******\n    // *************************************************************\n\n    /*\n\ttitle: string\n\tpos: [x,y]\n\tsize: [x,y]\n\n\tinput|output: every connection\n\t\t+  { name:string, type:string, pos: [x,y]=Optional, direction: \"input\"|\"output\", links: Array });\n\n\tgeneral properties:\n\t\t+ clip_area: if you render outside the node, it will be clipped\n\t\t+ unsafe_execution: not allowed for safe execution\n\t\t+ skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected\n\t\t+ resizable: if set to false it wont be resizable with the mouse\n\t\t+ horizontal: slots are distributed horizontally\n\t\t+ widgets_start_y: widgets start at y distance from the top of the node\n\t\n\tflags object:\n\t\t+ collapsed: if it is collapsed\n\n\tsupported callbacks:\n\t\t+ onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading)\n\t\t+ onRemoved: when removed from graph\n\t\t+ onStart:\twhen the graph starts playing\n\t\t+ onStop:\twhen the graph stops playing\n\t\t+ onDrawForeground: render the inside widgets inside the node\n\t\t+ onDrawBackground: render the background area inside the node (only in edit mode)\n\t\t+ onMouseDown\n\t\t+ onMouseMove\n\t\t+ onMouseUp\n\t\t+ onMouseEnter\n\t\t+ onMouseLeave\n\t\t+ onExecute: execute the node\n\t\t+ onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour)\n\t\t+ onGetInputs: returns an array of possible inputs\n\t\t+ onGetOutputs: returns an array of possible outputs\n\t\t+ onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h])\n\t\t+ onDblClick: double clicked in the node\n\t\t+ onInputDblClick: input slot double clicked (can be used to automatically create a node connected)\n\t\t+ onOutputDblClick: output slot double clicked (can be used to automatically create a node connected)\n\t\t+ onConfigure: called after the node has been configured\n\t\t+ onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data)\n\t\t+ onSelected\n\t\t+ onDeselected\n\t\t+ onDropItem : DOM item dropped over the node\n\t\t+ onDropFile : file dropped over the node\n\t\t+ onConnectInput : if returns false the incoming connection will be canceled\n\t\t+ onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info )\n\t\t+ onAction: action slot triggered\n\t\t+ getExtraMenuOptions: to add option to context menu\n*/\n\n    /**\n     * Base Class for all the node type classes\n     * @class LGraphNode\n     * @param {String} name a name for the node\n     */\n\n    function LGraphNode(title) {\n        this._ctor(title);\n    }\n\n    global.LGraphNode = LiteGraph.LGraphNode = LGraphNode;\n\n    LGraphNode.prototype._ctor = function(title) {\n        this.title = title || \"Unnamed\";\n        this.size = [LiteGraph.NODE_WIDTH, 60];\n        this.graph = null;\n\n        this._pos = new Float32Array(10, 10);\n\n        Object.defineProperty(this, \"pos\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._pos[0] = v[0];\n                this._pos[1] = v[1];\n            },\n            get: function() {\n                return this._pos;\n            },\n            enumerable: true\n        });\n\n        if (LiteGraph.use_uuids) {\n            this.id = LiteGraph.uuidv4();\n        }\n        else {\n            this.id = -1; //not know till not added\n        }\n        this.type = null;\n\n        //inputs available: array of inputs\n        this.inputs = [];\n        this.outputs = [];\n        this.connections = [];\n\n        //local data\n        this.properties = {}; //for the values\n        this.properties_info = []; //for the info\n\n        this.flags = {};\n    };\n\n    /**\n     * configure a node from an object containing the serialized info\n     * @method configure\n     */\n    LGraphNode.prototype.configure = function(info) {\n        if (this.graph) {\n            this.graph._version++;\n        }\n        for (var j in info) {\n            if (j == \"properties\") {\n                //i don't want to clone properties, I want to reuse the old container\n                for (var k in info.properties) {\n                    this.properties[k] = info.properties[k];\n                    if (this.onPropertyChanged) {\n                        this.onPropertyChanged( k, info.properties[k] );\n                    }\n                }\n                continue;\n            }\n\n            if (info[j] == null) {\n                continue;\n            } else if (typeof info[j] == \"object\") {\n                //object\n                if (this[j] && this[j].configure) {\n                    this[j].configure(info[j]);\n                } else {\n                    this[j] = LiteGraph.cloneObject(info[j], this[j]);\n                }\n            } //value\n            else {\n                this[j] = info[j];\n            }\n        }\n\n        if (!info.title) {\n            this.title = this.constructor.title;\n        }\n\n\t\tif (this.inputs) {\n\t\t\tfor (var i = 0; i < this.inputs.length; ++i) {\n\t\t\t\tvar input = this.inputs[i];\n\t\t\t\tvar link_info = this.graph ? this.graph.links[input.link] : null;\n\t\t\t\tif (this.onConnectionsChange)\n\t\t\t\t\tthis.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated\n\n\t\t\t\tif( this.onInputAdded )\n\t\t\t\t\tthis.onInputAdded(input);\n\n\t\t\t}\n\t\t}\n\n\t\tif (this.outputs) {\n\t\t\tfor (var i = 0; i < this.outputs.length; ++i) {\n\t\t\t\tvar output = this.outputs[i];\n\t\t\t\tif (!output.links) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfor (var j = 0; j < output.links.length; ++j) {\n\t\t\t\t\tvar link_info = this.graph \t? this.graph.links[output.links[j]] : null;\n\t\t\t\t\tif (this.onConnectionsChange)\n\t\t\t\t\t\tthis.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated\n\t\t\t\t}\n\n\t\t\t\tif( this.onOutputAdded )\n\t\t\t\t\tthis.onOutputAdded(output);\n\t\t\t}\n        }\n\n\t\tif( this.widgets )\n\t\t{\n\t\t\tfor (var i = 0; i < this.widgets.length; ++i)\n\t\t\t{\n\t\t\t\tvar w = this.widgets[i];\n\t\t\t\tif(!w)\n\t\t\t\t\tcontinue;\n\t\t\t\tif(w.options && w.options.property && (this.properties[ w.options.property ] != undefined))\n\t\t\t\t\tw.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) );\n\t\t\t}\n\t\t\tif (info.widgets_values) {\n\t\t\t\tfor (var i = 0; i < info.widgets_values.length; ++i) {\n\t\t\t\t\tif (this.widgets[i]) {\n\t\t\t\t\t\tthis.widgets[i].value = info.widgets_values[i];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n        if (this.onConfigure) {\n            this.onConfigure(info);\n        }\n    };\n\n    /**\n     * serialize the content\n     * @method serialize\n     */\n\n    LGraphNode.prototype.serialize = function() {\n        //create serialization object\n        var o = {\n            id: this.id,\n            type: this.type,\n            pos: this.pos,\n            size: this.size,\n            flags: LiteGraph.cloneObject(this.flags),\n\t\t\torder: this.order,\n            mode: this.mode\n        };\n\n        //special case for when there were errors\n        if (this.constructor === LGraphNode && this.last_serialization) {\n            return this.last_serialization;\n        }\n\n        if (this.inputs) {\n            o.inputs = this.inputs;\n        }\n\n        if (this.outputs) {\n            //clear outputs last data (because data in connections is never serialized but stored inside the outputs info)\n            for (var i = 0; i < this.outputs.length; i++) {\n                delete this.outputs[i]._data;\n            }\n            o.outputs = this.outputs;\n        }\n\n        if (this.title && this.title != this.constructor.title) {\n            o.title = this.title;\n        }\n\n        if (this.properties) {\n            o.properties = LiteGraph.cloneObject(this.properties);\n        }\n\n        if (this.widgets && this.serialize_widgets) {\n            o.widgets_values = [];\n            for (var i = 0; i < this.widgets.length; ++i) {\n\t\t\t\tif(this.widgets[i])\n\t                o.widgets_values[i] = this.widgets[i].value;\n\t\t\t\telse\n\t\t\t\t\to.widgets_values[i] = null;\n            }\n        }\n\n        if (!o.type) {\n            o.type = this.constructor.type;\n        }\n\n        if (this.color) {\n            o.color = this.color;\n        }\n        if (this.bgcolor) {\n            o.bgcolor = this.bgcolor;\n        }\n        if (this.boxcolor) {\n            o.boxcolor = this.boxcolor;\n        }\n        if (this.shape) {\n            o.shape = this.shape;\n        }\n\n        if (this.onSerialize) {\n            if (this.onSerialize(o)) {\n                console.warn(\n                    \"node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter\"\n                );\n            }\n        }\n\n        return o;\n    };\n\n    /* Creates a clone of this node */\n    LGraphNode.prototype.clone = function() {\n        var node = LiteGraph.createNode(this.type);\n        if (!node) {\n            return null;\n        }\n\n        //we clone it because serialize returns shared containers\n        var data = LiteGraph.cloneObject(this.serialize());\n\n        //remove links\n        if (data.inputs) {\n            for (var i = 0; i < data.inputs.length; ++i) {\n                data.inputs[i].link = null;\n            }\n        }\n\n        if (data.outputs) {\n            for (var i = 0; i < data.outputs.length; ++i) {\n                if (data.outputs[i].links) {\n                    data.outputs[i].links.length = 0;\n                }\n            }\n        }\n\n        delete data[\"id\"];\n\n        if (LiteGraph.use_uuids) {\n            data[\"id\"] = LiteGraph.uuidv4()\n        }\n\n        //remove links\n        node.configure(data);\n\n        return node;\n    };\n\n    /**\n     * serialize and stringify\n     * @method toString\n     */\n\n    LGraphNode.prototype.toString = function() {\n        return JSON.stringify(this.serialize());\n    };\n    //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph\n\n    /**\n     * get the title string\n     * @method getTitle\n     */\n\n    LGraphNode.prototype.getTitle = function() {\n        return this.title || this.constructor.title;\n    };\n\n    /**\n     * sets the value of a property\n     * @method setProperty\n     * @param {String} name\n     * @param {*} value\n     */\n    LGraphNode.prototype.setProperty = function(name, value) {\n        if (!this.properties) {\n            this.properties = {};\n        }\n\t\tif( value === this.properties[name] )\n\t\t\treturn;\n\t\tvar prev_value = this.properties[name];\n        this.properties[name] = value;\n        if (this.onPropertyChanged) {\n            if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change\n\t\t\t\tthis.properties[name] = prev_value;\n        }\n\t\tif(this.widgets) //widgets could be linked to properties\n\t\t\tfor(var i = 0; i < this.widgets.length; ++i)\n\t\t\t{\n\t\t\t\tvar w = this.widgets[i];\n\t\t\t\tif(!w)\n\t\t\t\t\tcontinue;\n\t\t\t\tif(w.options.property == name)\n\t\t\t\t{\n\t\t\t\t\tw.value = value;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n    };\n\n    // Execution *************************\n    /**\n     * sets the output data\n     * @method setOutputData\n     * @param {number} slot\n     * @param {*} data\n     */\n    LGraphNode.prototype.setOutputData = function(slot, data) {\n        if (!this.outputs) {\n            return;\n        }\n\n        //this maybe slow and a niche case\n        //if(slot && slot.constructor === String)\n        //\tslot = this.findOutputSlot(slot);\n\n        if (slot == -1 || slot >= this.outputs.length) {\n            return;\n        }\n\n        var output_info = this.outputs[slot];\n        if (!output_info) {\n            return;\n        }\n\n        //store data in the output itself in case we want to debug\n        output_info._data = data;\n\n        //if there are connections, pass the data to the connections\n        if (this.outputs[slot].links) {\n            for (var i = 0; i < this.outputs[slot].links.length; i++) {\n                var link_id = this.outputs[slot].links[i];\n\t\t\t\tvar link = this.graph.links[link_id];\n\t\t\t\tif(link)\n\t\t\t\t\tlink.data = data;\n            }\n        }\n    };\n\n    /**\n     * sets the output data type, useful when you want to be able to overwrite the data type\n     * @method setOutputDataType\n     * @param {number} slot\n     * @param {String} datatype\n     */\n    LGraphNode.prototype.setOutputDataType = function(slot, type) {\n        if (!this.outputs) {\n            return;\n        }\n        if (slot == -1 || slot >= this.outputs.length) {\n            return;\n        }\n        var output_info = this.outputs[slot];\n        if (!output_info) {\n            return;\n        }\n        //store data in the output itself in case we want to debug\n        output_info.type = type;\n\n        //if there are connections, pass the data to the connections\n        if (this.outputs[slot].links) {\n            for (var i = 0; i < this.outputs[slot].links.length; i++) {\n                var link_id = this.outputs[slot].links[i];\n                this.graph.links[link_id].type = type;\n            }\n        }\n    };\n\n    /**\n     * Retrieves the input data (data traveling through the connection) from one slot\n     * @method getInputData\n     * @param {number} slot\n     * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link\n     * @return {*} data or if it is not connected returns undefined\n     */\n    LGraphNode.prototype.getInputData = function(slot, force_update) {\n        if (!this.inputs) {\n            return;\n        } //undefined;\n\n        if (slot >= this.inputs.length || this.inputs[slot].link == null) {\n            return;\n        }\n\n        var link_id = this.inputs[slot].link;\n        var link = this.graph.links[link_id];\n        if (!link) {\n            //bug: weird case but it happens sometimes\n            return null;\n        }\n\n        if (!force_update) {\n            return link.data;\n        }\n\n        //special case: used to extract data from the incoming connection before the graph has been executed\n        var node = this.graph.getNodeById(link.origin_id);\n        if (!node) {\n            return link.data;\n        }\n\n        if (node.updateOutputData) {\n            node.updateOutputData(link.origin_slot);\n        } else if (node.onExecute) {\n            node.onExecute();\n        }\n\n        return link.data;\n    };\n\n    /**\n     * Retrieves the input data type (in case this supports multiple input types)\n     * @method getInputDataType\n     * @param {number} slot\n     * @return {String} datatype in string format\n     */\n    LGraphNode.prototype.getInputDataType = function(slot) {\n        if (!this.inputs) {\n            return null;\n        } //undefined;\n\n        if (slot >= this.inputs.length || this.inputs[slot].link == null) {\n            return null;\n        }\n        var link_id = this.inputs[slot].link;\n        var link = this.graph.links[link_id];\n        if (!link) {\n            //bug: weird case but it happens sometimes\n            return null;\n        }\n        var node = this.graph.getNodeById(link.origin_id);\n        if (!node) {\n            return link.type;\n        }\n        var output_info = node.outputs[link.origin_slot];\n        if (output_info) {\n            return output_info.type;\n        }\n        return null;\n    };\n\n    /**\n     * Retrieves the input data from one slot using its name instead of slot number\n     * @method getInputDataByName\n     * @param {String} slot_name\n     * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link\n     * @return {*} data or if it is not connected returns null\n     */\n    LGraphNode.prototype.getInputDataByName = function(\n        slot_name,\n        force_update\n    ) {\n        var slot = this.findInputSlot(slot_name);\n        if (slot == -1) {\n            return null;\n        }\n        return this.getInputData(slot, force_update);\n    };\n\n    /**\n     * tells you if there is a connection in one input slot\n     * @method isInputConnected\n     * @param {number} slot\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isInputConnected = function(slot) {\n        if (!this.inputs) {\n            return false;\n        }\n        return slot < this.inputs.length && this.inputs[slot].link != null;\n    };\n\n    /**\n     * tells you info about an input connection (which node, type, etc)\n     * @method getInputInfo\n     * @param {number} slot\n     * @return {Object} object or null { link: id, name: string, type: string or 0 }\n     */\n    LGraphNode.prototype.getInputInfo = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot < this.inputs.length) {\n            return this.inputs[slot];\n        }\n        return null;\n    };\n\n    /**\n     * Returns the link info in the connection of an input slot\n     * @method getInputLink\n     * @param {number} slot\n     * @return {LLink} object or null\n     */\n    LGraphNode.prototype.getInputLink = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot < this.inputs.length) {\n            var slot_info = this.inputs[slot];\n\t\t\treturn this.graph.links[ slot_info.link ];\n        }\n        return null;\n    };\n\n    /**\n     * returns the node connected in the input slot\n     * @method getInputNode\n     * @param {number} slot\n     * @return {LGraphNode} node or null\n     */\n    LGraphNode.prototype.getInputNode = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot >= this.inputs.length) {\n            return null;\n        }\n        var input = this.inputs[slot];\n        if (!input || input.link === null) {\n            return null;\n        }\n        var link_info = this.graph.links[input.link];\n        if (!link_info) {\n            return null;\n        }\n        return this.graph.getNodeById(link_info.origin_id);\n    };\n\n    /**\n     * returns the value of an input with this name, otherwise checks if there is a property with that name\n     * @method getInputOrProperty\n     * @param {string} name\n     * @return {*} value\n     */\n    LGraphNode.prototype.getInputOrProperty = function(name) {\n        if (!this.inputs || !this.inputs.length) {\n            return this.properties ? this.properties[name] : null;\n        }\n\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            var input_info = this.inputs[i];\n            if (name == input_info.name && input_info.link != null) {\n                var link = this.graph.links[input_info.link];\n                if (link) {\n                    return link.data;\n                }\n            }\n        }\n        return this.properties[name];\n    };\n\n    /**\n     * tells you the last output data that went in that slot\n     * @method getOutputData\n     * @param {number} slot\n     * @return {Object}  object or null\n     */\n    LGraphNode.prototype.getOutputData = function(slot) {\n        if (!this.outputs) {\n            return null;\n        }\n        if (slot >= this.outputs.length) {\n            return null;\n        }\n\n        var info = this.outputs[slot];\n        return info._data;\n    };\n\n    /**\n     * tells you info about an output connection (which node, type, etc)\n     * @method getOutputInfo\n     * @param {number} slot\n     * @return {Object}  object or null { name: string, type: string, links: [ ids of links in number ] }\n     */\n    LGraphNode.prototype.getOutputInfo = function(slot) {\n        if (!this.outputs) {\n            return null;\n        }\n        if (slot < this.outputs.length) {\n            return this.outputs[slot];\n        }\n        return null;\n    };\n\n    /**\n     * tells you if there is a connection in one output slot\n     * @method isOutputConnected\n     * @param {number} slot\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isOutputConnected = function(slot) {\n        if (!this.outputs) {\n            return false;\n        }\n        return (\n            slot < this.outputs.length &&\n            this.outputs[slot].links &&\n            this.outputs[slot].links.length\n        );\n    };\n\n    /**\n     * tells you if there is any connection in the output slots\n     * @method isAnyOutputConnected\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isAnyOutputConnected = function() {\n        if (!this.outputs) {\n            return false;\n        }\n        for (var i = 0; i < this.outputs.length; ++i) {\n            if (this.outputs[i].links && this.outputs[i].links.length) {\n                return true;\n            }\n        }\n        return false;\n    };\n\n    /**\n     * retrieves all the nodes connected to this output slot\n     * @method getOutputNodes\n     * @param {number} slot\n     * @return {array}\n     */\n    LGraphNode.prototype.getOutputNodes = function(slot) {\n        if (!this.outputs || this.outputs.length == 0) {\n            return null;\n        }\n\n        if (slot >= this.outputs.length) {\n            return null;\n        }\n\n        var output = this.outputs[slot];\n        if (!output.links || output.links.length == 0) {\n            return null;\n        }\n\n        var r = [];\n        for (var i = 0; i < output.links.length; i++) {\n            var link_id = output.links[i];\n            var link = this.graph.links[link_id];\n            if (link) {\n                var target_node = this.graph.getNodeById(link.target_id);\n                if (target_node) {\n                    r.push(target_node);\n                }\n            }\n        }\n        return r;\n    };\n\n    LGraphNode.prototype.addOnTriggerInput = function(){\n        var trigS = this.findInputSlot(\"onTrigger\");\n        if (trigS == -1){ //!trigS || \n            var input = this.addInput(\"onTrigger\", LiteGraph.EVENT, {optional: true, nameLocked: true});\n            return this.findInputSlot(\"onTrigger\");\n        }\n        return trigS;\n    }\n    \n    LGraphNode.prototype.addOnExecutedOutput = function(){\n        var trigS = this.findOutputSlot(\"onExecuted\");\n        if (trigS == -1){ //!trigS || \n            var output = this.addOutput(\"onExecuted\", LiteGraph.ACTION, {optional: true, nameLocked: true});\n            return this.findOutputSlot(\"onExecuted\");\n        }\n        return trigS;\n    }\n    \n    LGraphNode.prototype.onAfterExecuteNode = function(param, options){\n        var trigS = this.findOutputSlot(\"onExecuted\");\n        if (trigS != -1){\n            \n            //console.debug(this.id+\":\"+this.order+\" triggering slot onAfterExecute\");\n            //console.debug(param);\n            //console.debug(options);\n            this.triggerSlot(trigS, param, null, options);\n            \n        }\n    }    \n    \n    LGraphNode.prototype.changeMode = function(modeTo){\n        switch(modeTo){\n            case LiteGraph.ON_EVENT:\n                // this.addOnExecutedOutput();\n                break;\n                \n            case LiteGraph.ON_TRIGGER:\n                this.addOnTriggerInput();\n                this.addOnExecutedOutput();\n                break;\n                \n            case LiteGraph.NEVER:\n                break;\n                \n            case LiteGraph.ALWAYS:\n                break;\n                \n            case LiteGraph.ON_REQUEST:\n                break;\n            \n            default:\n                return false;\n                break;\n        }\n        this.mode = modeTo;\n        return true;\n    };\n\n    /**\n     * Triggers the execution of actions that were deferred when the action was triggered\n     * @method executePendingActions\n     */    \n    LGraphNode.prototype.executePendingActions = function() {\n        if(!this._waiting_actions || !this._waiting_actions.length)\n            return;\n        for(var i = 0; i < this._waiting_actions.length;++i)\n        {\n            var p = this._waiting_actions[i];\n            this.onAction(p[0],p[1],p[2],p[3],p[4]);\n        }        \n        this._waiting_actions.length = 0;\n    }\n\n    \n    /**\n     * Triggers the node code execution, place a boolean/counter to mark the node as being executed\n     * @method doExecute\n     * @param {*} param\n     * @param {*} options\n     */\n    LGraphNode.prototype.doExecute = function(param, options) {\n        options = options || {};\n        if (this.onExecute){\n            \n            // enable this to give the event an ID\n\t\t\tif (!options.action_call) options.action_call = this.id+\"_exec_\"+Math.floor(Math.random()*9999);\n            \n            this.graph.nodes_executing[this.id] = true; //.push(this.id);\n\n            this.onExecute(param, options);\n            \n            this.graph.nodes_executing[this.id] = false; //.pop();\n            \n            // save execution/action ref\n            this.exec_version = this.graph.iteration;\n            if(options && options.action_call){\n                this.action_call = options.action_call; // if (param)\n                this.graph.nodes_executedAction[this.id] = options.action_call;\n            }\n        }\n        else {\n        }\n        this.execute_triggered = 2; // the nFrames it will be used (-- each step), means \"how old\" is the event\n        if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback\n    };\n    \n    /**\n     * Triggers an action, wrapped by logics to control execution flow\n     * @method actionDo\n     * @param {String} action name\n     * @param {*} param\n     */\n    LGraphNode.prototype.actionDo = function(action, param, options, action_slot ) {\n        options = options || {};\n        if (this.onAction){\n            \n\t\t\t// enable this to give the event an ID\n            if (!options.action_call) options.action_call = this.id+\"_\"+(action?action:\"action\")+\"_\"+Math.floor(Math.random()*9999);\n            \n            this.graph.nodes_actioning[this.id] = (action?action:\"actioning\"); //.push(this.id);\n            \n            this.onAction(action, param, options, action_slot);\n            \n            this.graph.nodes_actioning[this.id] = false; //.pop();\n            \n            // save execution/action ref\n            if(options && options.action_call){\n                this.action_call = options.action_call; // if (param)\n                this.graph.nodes_executedAction[this.id] = options.action_call;\n            }\n        }\n        this.action_triggered = 2; // the nFrames it will be used (-- each step), means \"how old\" is the event\n        if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options);\n    };\n    \n    /**\n     * Triggers an event in this node, this will trigger any output with the same name\n     * @method trigger\n     * @param {String} event name ( \"on_play\", ... ) if action is equivalent to false then the event is send to all\n     * @param {*} param\n     */\n    LGraphNode.prototype.trigger = function(action, param, options) {\n        if (!this.outputs || !this.outputs.length) {\n            return;\n        }\n\n        if (this.graph)\n            this.graph._last_trigger_time = LiteGraph.getTime();\n\n        for (var i = 0; i < this.outputs.length; ++i) {\n            var output = this.outputs[i];\n            if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) )\n                continue;\n            this.triggerSlot(i, param, null, options);\n        }\n    };\n\n    /**\n     * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes\n     * @method triggerSlot\n     * @param {Number} slot the index of the output slot\n     * @param {*} param\n     * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot\n     */\n    LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) {\n        options = options || {};\n        if (!this.outputs) {\n            return;\n        }\n\n\t\tif(slot == null)\n\t\t{\n\t\t\tconsole.error(\"slot must be a number\");\n\t\t\treturn;\n\t\t}\n\n\t\tif(slot.constructor !== Number)\n\t\t\tconsole.warn(\"slot must be a number, use node.trigger('name') if you want to use a string\");\n\n        var output = this.outputs[slot];\n        if (!output) {\n            return;\n        }\n\n        var links = output.links;\n        if (!links || !links.length) {\n            return;\n        }\n\n        if (this.graph) {\n            this.graph._last_trigger_time = LiteGraph.getTime();\n        }\n\n        //for every link attached here\n        for (var k = 0; k < links.length; ++k) {\n            var id = links[k];\n            if (link_id != null && link_id != id) {\n                //to skip links\n                continue;\n            }\n            var link_info = this.graph.links[links[k]];\n            if (!link_info) {\n                //not connected\n                continue;\n            }\n            link_info._last_time = LiteGraph.getTime();\n            var node = this.graph.getNodeById(link_info.target_id);\n            if (!node) {\n                //node not found?\n                continue;\n            }\n\n            //used to mark events in graph\n            var target_connection = node.inputs[link_info.target_slot];\n\n\t\t\tif (node.mode === LiteGraph.ON_TRIGGER)\n\t\t\t{\n\t\t\t\t// generate unique trigger ID if not present\n\t\t\t\tif (!options.action_call) options.action_call = this.id+\"_trigg_\"+Math.floor(Math.random()*9999);\n                if (node.onExecute) {\n                    // -- wrapping node.onExecute(param); --\n                    node.doExecute(param, options);\n                }\n\t\t\t}\n\t\t\telse if (node.onAction) {\n                // generate unique action ID if not present\n\t\t\t\tif (!options.action_call) options.action_call = this.id+\"_act_\"+Math.floor(Math.random()*9999);\n                //pass the action name\n                var target_connection = node.inputs[link_info.target_slot];\n\n                //instead of executing them now, it will be executed in the next graph loop, to ensure data flow\n                if(LiteGraph.use_deferred_actions && node.onExecute)\n                {\n                    if(!node._waiting_actions)\n                        node._waiting_actions = [];\n                    node._waiting_actions.push([target_connection.name, param, options, link_info.target_slot]);\n                }\n                else\n                {\n                    // wrap node.onAction(target_connection.name, param);\n                    node.actionDo( target_connection.name, param, options, link_info.target_slot );\n                }\n            }\n        }\n    };\n\n    /**\n     * clears the trigger slot animation\n     * @method clearTriggeredSlot\n     * @param {Number} slot the index of the output slot\n     * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot\n     */\n    LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) {\n        if (!this.outputs) {\n            return;\n        }\n\n        var output = this.outputs[slot];\n        if (!output) {\n            return;\n        }\n\n        var links = output.links;\n        if (!links || !links.length) {\n            return;\n        }\n\n        //for every link attached here\n        for (var k = 0; k < links.length; ++k) {\n            var id = links[k];\n            if (link_id != null && link_id != id) {\n                //to skip links\n                continue;\n            }\n            var link_info = this.graph.links[links[k]];\n            if (!link_info) {\n                //not connected\n                continue;\n            }\n            link_info._last_time = 0;\n        }\n    };\n\n    /**\n     * changes node size and triggers callback\n     * @method setSize\n     * @param {vec2} size\n     */\n    LGraphNode.prototype.setSize = function(size)\n\t{\n\t\tthis.size = size;\n\t\tif(this.onResize)\n\t\t\tthis.onResize(this.size);\n\t}\n\n    /**\n     * add a new property to this node\n     * @method addProperty\n     * @param {string} name\n     * @param {*} default_value\n     * @param {string} type string defining the output type (\"vec3\",\"number\",...)\n     * @param {Object} extra_info this can be used to have special properties of the property (like values, etc)\n     */\n    LGraphNode.prototype.addProperty = function(\n        name,\n        default_value,\n        type,\n        extra_info\n    ) {\n        var o = { name: name, type: type, default_value: default_value };\n        if (extra_info) {\n            for (var i in extra_info) {\n                o[i] = extra_info[i];\n            }\n        }\n        if (!this.properties_info) {\n            this.properties_info = [];\n        }\n        this.properties_info.push(o);\n        if (!this.properties) {\n            this.properties = {};\n        }\n        this.properties[name] = default_value;\n        return o;\n    };\n\n    //connections\n\n    /**\n     * add a new output slot to use in this node\n     * @method addOutput\n     * @param {string} name\n     * @param {string} type string defining the output type (\"vec3\",\"number\",...)\n     * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc)\n     */\n    LGraphNode.prototype.addOutput = function(name, type, extra_info) {\n        var output = { name: name, type: type, links: null };\n        if (extra_info) {\n            for (var i in extra_info) {\n                output[i] = extra_info[i];\n            }\n        }\n\n        if (!this.outputs) {\n            this.outputs = [];\n        }\n        this.outputs.push(output);\n        if (this.onOutputAdded) {\n            this.onOutputAdded(output);\n        }\n        \n        if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true);\n        \n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n        return output;\n    };\n\n    /**\n     * add a new output slot to use in this node\n     * @method addOutputs\n     * @param {Array} array of triplets like [[name,type,extra_info],[...]]\n     */\n    LGraphNode.prototype.addOutputs = function(array) {\n        for (var i = 0; i < array.length; ++i) {\n            var info = array[i];\n            var o = { name: info[0], type: info[1], link: null };\n            if (array[2]) {\n                for (var j in info[2]) {\n                    o[j] = info[2][j];\n                }\n            }\n\n            if (!this.outputs) {\n                this.outputs = [];\n            }\n            this.outputs.push(o);\n            if (this.onOutputAdded) {\n                this.onOutputAdded(o);\n            }\n            \n            if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true);\n            \n        }\n\n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * remove an existing output slot\n     * @method removeOutput\n     * @param {number} slot\n     */\n    LGraphNode.prototype.removeOutput = function(slot) {\n        this.disconnectOutput(slot);\n        this.outputs.splice(slot, 1);\n        for (var i = slot; i < this.outputs.length; ++i) {\n            if (!this.outputs[i] || !this.outputs[i].links) {\n                continue;\n            }\n            var links = this.outputs[i].links;\n            for (var j = 0; j < links.length; ++j) {\n                var link = this.graph.links[links[j]];\n                if (!link) {\n                    continue;\n                }\n                link.origin_slot -= 1;\n            }\n        }\n\n        this.setSize( this.computeSize() );\n        if (this.onOutputRemoved) {\n            this.onOutputRemoved(slot);\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * add a new input slot to use in this node\n     * @method addInput\n     * @param {string} name\n     * @param {string} type string defining the input type (\"vec3\",\"number\",...), it its a generic one use 0\n     * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc)\n     */\n    LGraphNode.prototype.addInput = function(name, type, extra_info) {\n        type = type || 0;\n        var input = { name: name, type: type, link: null };\n        if (extra_info) {\n            for (var i in extra_info) {\n                input[i] = extra_info[i];\n            }\n        }\n\n        if (!this.inputs) {\n            this.inputs = [];\n        }\n\n        this.inputs.push(input);\n        this.setSize( this.computeSize() );\n\n        if (this.onInputAdded) {\n            this.onInputAdded(input);\n\t\t}\n        \n        LiteGraph.registerNodeAndSlotType(this,type);\n\n        this.setDirtyCanvas(true, true);\n        return input;\n    };\n\n    /**\n     * add several new input slots in this node\n     * @method addInputs\n     * @param {Array} array of triplets like [[name,type,extra_info],[...]]\n     */\n    LGraphNode.prototype.addInputs = function(array) {\n        for (var i = 0; i < array.length; ++i) {\n            var info = array[i];\n            var o = { name: info[0], type: info[1], link: null };\n            if (array[2]) {\n                for (var j in info[2]) {\n                    o[j] = info[2][j];\n                }\n            }\n\n            if (!this.inputs) {\n                this.inputs = [];\n            }\n            this.inputs.push(o);\n            if (this.onInputAdded) {\n                this.onInputAdded(o);\n            }\n            \n            LiteGraph.registerNodeAndSlotType(this,info[1]);\n        }\n\n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * remove an existing input slot\n     * @method removeInput\n     * @param {number} slot\n     */\n    LGraphNode.prototype.removeInput = function(slot) {\n        this.disconnectInput(slot);\n        var slot_info = this.inputs.splice(slot, 1);\n        for (var i = slot; i < this.inputs.length; ++i) {\n            if (!this.inputs[i]) {\n                continue;\n            }\n            var link = this.graph.links[this.inputs[i].link];\n            if (!link) {\n                continue;\n            }\n            link.target_slot -= 1;\n        }\n        this.setSize( this.computeSize() );\n        if (this.onInputRemoved) {\n            this.onInputRemoved(slot, slot_info[0] );\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * add an special connection to this node (used for special kinds of graphs)\n     * @method addConnection\n     * @param {string} name\n     * @param {string} type string defining the input type (\"vec3\",\"number\",...)\n     * @param {[x,y]} pos position of the connection inside the node\n     * @param {string} direction if is input or output\n     */\n    LGraphNode.prototype.addConnection = function(name, type, pos, direction) {\n        var o = {\n            name: name,\n            type: type,\n            pos: pos,\n            direction: direction,\n            links: null\n        };\n        this.connections.push(o);\n        return o;\n    };\n\n    /**\n     * computes the minimum size of a node according to its inputs and output slots\n     * @method computeSize\n     * @param {vec2} minHeight\n     * @return {vec2} the total size\n     */\n    LGraphNode.prototype.computeSize = function(out) {\n        if (this.constructor.size) {\n            return this.constructor.size.concat();\n        }\n\n        var rows = Math.max(\n            this.inputs ? this.inputs.length : 1,\n            this.outputs ? this.outputs.length : 1\n        );\n        var size = out || new Float32Array([0, 0]);\n        rows = Math.max(rows, 1);\n        var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size\n\n        var title_width = compute_text_size(this.title);\n        var input_width = 0;\n        var output_width = 0;\n\n        if (this.inputs) {\n            for (var i = 0, l = this.inputs.length; i < l; ++i) {\n                var input = this.inputs[i];\n                var text = input.label || input.name || \"\";\n                var text_width = compute_text_size(text);\n                if (input_width < text_width) {\n                    input_width = text_width;\n                }\n            }\n        }\n\n        if (this.outputs) {\n            for (var i = 0, l = this.outputs.length; i < l; ++i) {\n                var output = this.outputs[i];\n                var text = output.label || output.name || \"\";\n                var text_width = compute_text_size(text);\n                if (output_width < text_width) {\n                    output_width = text_width;\n                }\n            }\n        }\n\n        size[0] = Math.max(input_width + output_width + 10, title_width);\n        size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH);\n        if (this.widgets && this.widgets.length) {\n            size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5);\n        }\n\n        size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT;\n\n        var widgets_height = 0;\n        if (this.widgets && this.widgets.length) {\n            for (var i = 0, l = this.widgets.length; i < l; ++i) {\n                if (this.widgets[i].computeSize)\n                    widgets_height += this.widgets[i].computeSize(size[0])[1] + 4;\n                else\n                    widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4;\n            }\n            widgets_height += 8;\n        }\n\n        //compute height using widgets height\n        if( this.widgets_up )\n            size[1] = Math.max( size[1], widgets_height );\n        else if( this.widgets_start_y != null )\n            size[1] = Math.max( size[1], widgets_height + this.widgets_start_y );\n        else\n            size[1] += widgets_height;\n\n        function compute_text_size(text) {\n            if (!text) {\n                return 0;\n            }\n            return font_size * text.length * 0.6;\n        }\n\n        if (\n            this.constructor.min_height &&\n            size[1] < this.constructor.min_height\n        ) {\n            size[1] = this.constructor.min_height;\n        }\n\n        size[1] += 6; //margin\n\n        return size;\n    };\n\n    /**\n     * returns all the info available about a property of this node.\n     *\n     * @method getPropertyInfo\n     * @param {String} property name of the property\n     * @return {Object} the object with all the available info\n    */\n    LGraphNode.prototype.getPropertyInfo = function( property )\n\t{\n        var info = null;\n\n\t\t//there are several ways to define info about a property\n\t\t//legacy mode\n\t\tif (this.properties_info) {\n            for (var i = 0; i < this.properties_info.length; ++i) {\n                if (this.properties_info[i].name == property) {\n                    info = this.properties_info[i];\n                    break;\n                }\n            }\n        }\n\t\t//litescene mode using the constructor\n\t\tif(this.constructor[\"@\" + property])\n\t\t\tinfo = this.constructor[\"@\" + property];\n\n\t\tif(this.constructor.widgets_info && this.constructor.widgets_info[property])\n\t\t\tinfo = this.constructor.widgets_info[property];\n\n\t\t//litescene mode using the constructor\n\t\tif (!info && this.onGetPropertyInfo) {\n            info = this.onGetPropertyInfo(property);\n        }\n\n        if (!info)\n            info = {};\n\t\tif(!info.type)\n\t\t\tinfo.type = typeof this.properties[property];\n\t\tif(info.widget == \"combo\")\n\t\t\tinfo.type = \"enum\";\n\n\t\treturn info;\n\t}\n\n    /**\n     * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties\n     *\n     * @method addWidget\n     * @param {String} type the widget type (could be \"number\",\"string\",\"combo\"\n     * @param {String} name the text to show on the widget\n     * @param {String} value the default value\n     * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify)\n     * @param {Object} options the object that contains special properties of this widget \n     * @return {Object} the created widget object\n     */\n    LGraphNode.prototype.addWidget = function( type, name, value, callback, options )\n\t{\n        if (!this.widgets) {\n            this.widgets = [];\n        }\n\n\t\tif(!options && callback && callback.constructor === Object)\n\t\t{\n\t\t\toptions = callback;\n\t\t\tcallback = null;\n\t\t}\n\n\t\tif(options && options.constructor === String) //options can be the property name\n\t\t\toptions = { property: options };\n\n\t\tif(callback && callback.constructor === String) //callback can be the property name\n\t\t{\n\t\t\tif(!options)\n\t\t\t\toptions = {};\n\t\t\toptions.property = callback;\n\t\t\tcallback = null;\n\t\t}\n\n\t\tif(callback && callback.constructor !== Function)\n\t\t{\n\t\t\tconsole.warn(\"addWidget: callback must be a function\");\n\t\t\tcallback = null;\n\t\t}\n\n        var w = {\n            type: type.toLowerCase(),\n            name: name,\n            value: value,\n            callback: callback,\n            options: options || {}\n        };\n\n        if (w.options.y !== undefined) {\n            w.y = w.options.y;\n        }\n\n        if (!callback && !w.options.callback && !w.options.property) {\n            console.warn(\"LiteGraph addWidget(...) without a callback or property assigned\");\n        }\n        if (type == \"combo\" && !w.options.values) {\n            throw \"LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }\";\n        }\n        this.widgets.push(w);\n\t\tthis.setSize( this.computeSize() );\n        return w;\n    };\n\n    LGraphNode.prototype.addCustomWidget = function(custom_widget) {\n        if (!this.widgets) {\n            this.widgets = [];\n        }\n        this.widgets.push(custom_widget);\n        return custom_widget;\n    };\n\n    /**\n     * returns the bounding of the object, used for rendering purposes\n     * @method getBounding\n     * @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage\n     * @param compute_outer {boolean?} [optional] set to true to include the shadow and connection points in the bounding calculation\n     * @return {Float32Array[4]} the bounding box in format of [topleft_cornerx, topleft_cornery, width, height]\n     */\n    LGraphNode.prototype.getBounding = function(out, compute_outer) {\n        out = out || new Float32Array(4);\n        const nodePos = this.pos;\n        const isCollapsed = this.flags.collapsed;\n        const nodeSize = this.size;\n        \n        let left_offset = 0;\n        // 1 offset due to how nodes are rendered\n        let right_offset =  1 ;\n        let top_offset = 0;\n        let bottom_offset = 0;\n        \n        if (compute_outer) {\n            // 4 offset for collapsed node connection points\n            left_offset = 4;\n            // 6 offset for right shadow and collapsed node connection points\n            right_offset = 6 + left_offset;\n            // 4 offset for collapsed nodes top connection points\n            top_offset = 4;\n            // 5 offset for bottom shadow and collapsed node connection points\n            bottom_offset = 5 + top_offset;\n        }\n        \n        out[0] = nodePos[0] - left_offset;\n        out[1] = nodePos[1] - LiteGraph.NODE_TITLE_HEIGHT - top_offset;\n        out[2] = isCollapsed ?\n            (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + right_offset :\n            nodeSize[0] + right_offset;\n        out[3] = isCollapsed ?\n            LiteGraph.NODE_TITLE_HEIGHT + bottom_offset :\n            nodeSize[1] + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset;\n\n        if (this.onBounding) {\n            this.onBounding(out);\n        }\n        return out;\n    };\n\n    /**\n     * checks if a point is inside the shape of a node\n     * @method isPointInside\n     * @param {number} x\n     * @param {number} y\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) {\n        margin = margin || 0;\n\n        var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT;\n        if (skip_title) {\n            margin_top = 0;\n        }\n        if (this.flags && this.flags.collapsed) {\n            //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS)\n            if (\n                isInsideRectangle(\n                    x,\n                    y,\n                    this.pos[0] - margin,\n                    this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin,\n                    (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) +\n                        2 * margin,\n                    LiteGraph.NODE_TITLE_HEIGHT + 2 * margin\n                )\n            ) {\n                return true;\n            }\n        } else if (\n            this.pos[0] - 4 - margin < x &&\n            this.pos[0] + this.size[0] + 4 + margin > x &&\n            this.pos[1] - margin_top - margin < y &&\n            this.pos[1] + this.size[1] + margin > y\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    /**\n     * checks if a point is inside a node slot, and returns info about which slot\n     * @method getSlotInPosition\n     * @param {number} x\n     * @param {number} y\n     * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }\n     */\n    LGraphNode.prototype.getSlotInPosition = function(x, y) {\n        //search for inputs\n        var link_pos = new Float32Array(2);\n        if (this.inputs) {\n            for (var i = 0, l = this.inputs.length; i < l; ++i) {\n                var input = this.inputs[i];\n                this.getConnectionPos(true, i, link_pos);\n                if (\n                    isInsideRectangle(\n                        x,\n                        y,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        20,\n                        10\n                    )\n                ) {\n                    return { input: input, slot: i, link_pos: link_pos };\n                }\n            }\n        }\n\n        if (this.outputs) {\n            for (var i = 0, l = this.outputs.length; i < l; ++i) {\n                var output = this.outputs[i];\n                this.getConnectionPos(false, i, link_pos);\n                if (\n                    isInsideRectangle(\n                        x,\n                        y,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        20,\n                        10\n                    )\n                ) {\n                    return { output: output, slot: i, link_pos: link_pos };\n                }\n            }\n        }\n\n        return null;\n    };\n\n    /**\n     * returns the input slot with a given name (used for dynamic slots), -1 if not found\n     * @method findInputSlot\n     * @param {string} name the name of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findInputSlot = function(name,  returnObj) {\n        if (!this.inputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            if (name == this.inputs[i].name) {\n                return !returnObj ? i : this.inputs[i];\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * returns the output slot with a given name (used for dynamic slots), -1 if not found\n     * @method findOutputSlot\n     * @param {string} name the name of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findOutputSlot = function(name, returnObj) {\n        returnObj = returnObj || false;\n        if (!this.outputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            if (name == this.outputs[i].name) {\n                return !returnObj ? i : this.outputs[i];\n            }\n        }\n        return -1;\n    };\n    \n    // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options\n    \n    /**\n     * returns the first free input slot\n     * @method findInputSlotFree\n     * @param {object} options\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findInputSlotFree = function(optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = {returnObj: false\n                        ,typesNotAccepted: []\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (!this.inputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            if (this.inputs[i].link && this.inputs[i].link != null) {\n                continue;\n            }\n            if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){\n                continue;\n            }\n            return !opts.returnObj ? i : this.inputs[i];\n        }\n        return -1;\n    };\n\n    /**\n     * returns the first output slot free\n     * @method findOutputSlotFree\n     * @param {object} options\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findOutputSlotFree = function(optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { returnObj: false\n                        ,typesNotAccepted: []\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (!this.outputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            if (this.outputs[i].links && this.outputs[i].links != null) {\n                continue;\n            }\n            if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){\n                continue;\n            }\n            return !opts.returnObj ? i : this.outputs[i];\n        }\n        return -1;\n    };\n    \n    /**\n     * findSlotByType for INPUTS\n     */\n    LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied);\n    };\n\n    /**\n     * findSlotByType for OUTPUTS\n     */\n    LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied);\n    };\n    \n    /**\n     * returns the output (or input) slot with a given type, -1 if not found\n     * @method findSlotByType\n     * @param {boolean} input uise inputs instead of outputs\n     * @param {string} type the type of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway)\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        input = input || false;\n        returnObj = returnObj || false;\n        preferFreeSlot = preferFreeSlot || false;\n        doNotUseOccupied = doNotUseOccupied || false;\n        var aSlots = input ? this.inputs : this.outputs;\n        if (!aSlots) {\n            return -1;\n        }\n\t\t// !! empty string type is considered 0, * !!\n\t\tif (type == \"\" || type == \"*\") type = 0; \n        for (var i = 0, l = aSlots.length; i < l; ++i) {\n            var tFound = false;\n            var aSource = (type+\"\").toLowerCase().split(\",\");\n            var aDest = aSlots[i].type==\"0\"||aSlots[i].type==\"*\"?\"0\":aSlots[i].type;\n\t\t\taDest = (aDest+\"\").toLowerCase().split(\",\");\n            for(var sI=0;sI<aSource.length;sI++){\n                for(var dI=0;dI<aDest.length;dI++){\n\t\t\t\t\tif (aSource[sI]==\"_event_\") aSource[sI] = LiteGraph.EVENT;\n\t\t\t\t\tif (aDest[sI]==\"_event_\") aDest[sI] = LiteGraph.EVENT;\n\t\t\t\t\tif (aSource[sI]==\"*\") aSource[sI] = 0;\n\t\t\t\t\tif (aDest[sI]==\"*\") aDest[sI] = 0;\n\t\t\t\t\tif (aSource[sI] == aDest[dI]) {\n                        if (preferFreeSlot && aSlots[i].links && aSlots[i].links !== null) continue;\n                        return !returnObj ? i : aSlots[i];\n                    }\n                }\n            }\n        }\n        // if didnt find some, stop checking for free slots\n        if (preferFreeSlot && !doNotUseOccupied){\n            for (var i = 0, l = aSlots.length; i < l; ++i) {\n                var tFound = false;\n                var aSource = (type+\"\").toLowerCase().split(\",\");\n                var aDest = aSlots[i].type==\"0\"||aSlots[i].type==\"*\"?\"0\":aSlots[i].type;\n\t\t\t\taDest = (aDest+\"\").toLowerCase().split(\",\");\n                for(var sI=0;sI<aSource.length;sI++){\n                    for(var dI=0;dI<aDest.length;dI++){\n\t\t\t\t\t\tif (aSource[sI]==\"*\") aSource[sI] = 0;\n\t\t\t\t\t\tif (aDest[sI]==\"*\") aDest[sI] = 0;\n                        if (aSource[sI] == aDest[dI]) {\n                            return !returnObj ? i : aSlots[i];\n                        }\n                    }\n                }\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * connect this node output to the input of another node BY TYPE\n     * @method connectByType\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {string} target_type the input slot type of the target node\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connectByType = function(slot, target_node, target_slotType, optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { createEventInCase: true\n\t\t\t\t\t   \t,firstFreeIfOutputGeneralInCase: true\n                        ,generalTypeInCase: true\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (target_node && target_node.constructor === Number) {\n            target_node = this.graph.getNodeById(target_node);\n        }\n        var target_slot = target_node.findInputSlotByType(target_slotType, false, true);\n        if (target_slot >= 0 && target_slot !== null){\n            //console.debug(\"CONNbyTYPE type \"+target_slotType+\" for \"+target_slot)\n            return this.connect(slot, target_node, target_slot);\n        }else{\n            //console.log(\"type \"+target_slotType+\" not found or not free?\")\n            if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){\n                // WILL CREATE THE onTrigger IN SLOT\n\t\t\t\t//console.debug(\"connect WILL CREATE THE onTrigger \"+target_slotType+\" to \"+target_node);\n                return this.connect(slot, target_node, -1);\n            }\n\t\t\t// connect to the first general output slot if not found a specific type and \n            if (opts.generalTypeInCase){\n                var target_slot = target_node.findInputSlotByType(0, false, true, true);\n\t\t\t\t//console.debug(\"connect TO a general type (*, 0), if not found the specific type \",target_slotType,\" to \",target_node,\"RES_SLOT:\",target_slot);\n                if (target_slot >= 0){\n                    return this.connect(slot, target_node, target_slot);\n                }\n            }\n            // connect to the first free input slot if not found a specific type and this output is general\n            if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == \"*\" || target_slotType == \"\")){\n                var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] });\n\t\t\t\t//console.debug(\"connect TO TheFirstFREE \",target_slotType,\" to \",target_node,\"RES_SLOT:\",target_slot);\n                if (target_slot >= 0){\n\t\t\t\t\treturn this.connect(slot, target_node, target_slot);\n                }\n            }\n\t\t\t\n\t\t\tconsole.debug(\"no way to connect type: \",target_slotType,\" to targetNODE \",target_node);\n\t\t\t//TODO filter\n\t\t\t\n            return null;\n        }\n    }\n    \n    /**\n     * connect this node input to the output of another node BY TYPE\n     * @method connectByType\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {string} target_type the output slot type of the target node\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { createEventInCase: true\n                        ,firstFreeIfInputGeneralInCase: true\n                        ,generalTypeInCase: true\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (source_node && source_node.constructor === Number) {\n            source_node = this.graph.getNodeById(source_node);\n        }\n        var source_slot = source_node.findOutputSlotByType(source_slotType, false, true);\n        if (source_slot >= 0 && source_slot !== null){\n            //console.debug(\"CONNbyTYPE OUT! type \"+source_slotType+\" for \"+source_slot)\n            return source_node.connect(source_slot, this, slot);\n        }else{\n            \n            // connect to the first general output slot if not found a specific type and \n            if (opts.generalTypeInCase){\n                var source_slot = source_node.findOutputSlotByType(0, false, true, true);\n                if (source_slot >= 0){\n                    return source_node.connect(source_slot, this, slot);\n                }\n            }\n            \n            if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){\n                // WILL CREATE THE onExecuted OUT SLOT\n\t\t\t\tif (LiteGraph.do_add_triggers_slots){\n\t\t\t\t\tvar source_slot = source_node.addOnExecutedOutput();\n\t\t\t\t\treturn source_node.connect(source_slot, this, slot);\n\t\t\t\t}\n            }\n            // connect to the first free output slot if not found a specific type and this input is general\n            if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == \"*\" || source_slotType == \"\")){\n                var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] });\n                if (source_slot >= 0){\n                    return source_node.connect(source_slot, this, slot);\n                }\n            }\n            \n\t\t\tconsole.debug(\"no way to connect byOUT type: \",source_slotType,\" to sourceNODE \",source_node);\n\t\t\t//TODO filter\n\t\t\t\n            //console.log(\"type OUT! \"+source_slotType+\" not found or not free?\")\n            return null;\n        }\n    }\n    \n    /**\n     * connect this node output to the input of another node\n     * @method connect\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connect = function(slot, target_node, target_slot) {\n        target_slot = target_slot || 0;\n\n        if (!this.graph) {\n            //could be connected before adding it to a graph\n            console.log(\n                \"Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them.\"\n            ); //due to link ids being associated with graphs\n            return null;\n        }\n\n        //seek for the output slot\n        if (slot.constructor === String) {\n            slot = this.findOutputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return null;\n            }\n        } else if (!this.outputs || slot >= this.outputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return null;\n        }\n\n        if (target_node && target_node.constructor === Number) {\n            target_node = this.graph.getNodeById(target_node);\n        }\n        if (!target_node) {\n            throw \"target node is null\";\n        }\n\n        //avoid loopback\n        if (target_node == this) {\n            return null;\n        }\n\n        //you can specify the slot by name\n        if (target_slot.constructor === String) {\n            target_slot = target_node.findInputSlot(target_slot);\n            if (target_slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\n                        \"Connect: Error, no slot of name \" + target_slot\n                    );\n                }\n                return null;\n            }\n        } else if (target_slot === LiteGraph.EVENT) {\n            \n            if (LiteGraph.do_add_triggers_slots){\n\t            //search for first slot with event? :: NO this is done outside\n\t\t\t\t//console.log(\"Connect: Creating triggerEvent\");\n\t            // force mode\n\t            target_node.changeMode(LiteGraph.ON_TRIGGER);\n\t            target_slot = target_node.findInputSlot(\"onTrigger\");\n        \t}else{\n            \treturn null; // -- break --\n\t\t\t}\n        } else if (\n            !target_node.inputs ||\n            target_slot >= target_node.inputs.length\n        ) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return null;\n        }\n\n\t\tvar changed = false;\n\n        var input = target_node.inputs[target_slot];\n        var link_info = null;\n        var output = this.outputs[slot];\n        \n        if (!this.outputs[slot]){\n            /*console.debug(\"Invalid slot passed: \"+slot);\n            console.debug(this.outputs);*/\n            return null;\n        }\n\n        // allow target node to change slot\n        if (target_node.onBeforeConnectInput) {\n            // This way node can choose another slot (or make a new one?)\n            target_slot = target_node.onBeforeConnectInput(target_slot); //callback\n        }\n\n\t\t//check target_slot and check connection types\n        if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type))\n\t\t{\n\t        this.setDirtyCanvas(false, true);\n\t\t\tif(changed)\n\t\t        this.graph.connectionChange(this, link_info);\n\t\t\treturn null;\n\t\t}else{\n\t\t\t//console.debug(\"valid connection\",output.type, input.type);\n\t\t}\n\n        //allows nodes to block connection, callback\n        if (target_node.onConnectInput) {\n            if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) {\n                return null;\n            }\n        }\n        if (this.onConnectOutput) { // callback\n            if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) {\n                return null;\n            }\n        }\n\n        //if there is something already plugged there, disconnect\n        if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) {\n\t\t\tthis.graph.beforeChange();\n            target_node.disconnectInput(target_slot, {doProcessChange: false});\n\t\t\tchanged = true;\n        }\n        if (output.links !== null && output.links.length){\n            switch(output.type){\n                case LiteGraph.EVENT:\n                    if (!LiteGraph.allow_multi_output_for_events){\n                        this.graph.beforeChange();\n                        this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false});\n                        changed = true;\n                    }\n                break;\n                default:\n                break;\n            }\n        }\n\n        var nextId\n        if (LiteGraph.use_uuids)\n            nextId = LiteGraph.uuidv4();\n        else\n            nextId = ++this.graph.last_link_id;\n        \n\t\t//create link class\n\t\tlink_info = new LLink(\n\t\t\tnextId,\n\t\t\tinput.type || output.type,\n\t\t\tthis.id,\n\t\t\tslot,\n\t\t\ttarget_node.id,\n\t\t\ttarget_slot\n\t\t);\n\n\t\t//add to graph links list\n\t\tthis.graph.links[link_info.id] = link_info;\n\n\t\t//connect in output\n\t\tif (output.links == null) {\n\t\t\toutput.links = [];\n\t\t}\n\t\toutput.links.push(link_info.id);\n\t\t//connect in input\n\t\ttarget_node.inputs[target_slot].link = link_info.id;\n\t\tif (this.graph) {\n\t\t\tthis.graph._version++;\n\t\t}\n\t\tif (this.onConnectionsChange) {\n\t\t\tthis.onConnectionsChange(\n\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\tslot,\n\t\t\t\ttrue,\n\t\t\t\tlink_info,\n\t\t\t\toutput\n\t\t\t);\n\t\t} //link_info has been created now, so its updated\n\t\tif (target_node.onConnectionsChange) {\n\t\t\ttarget_node.onConnectionsChange(\n\t\t\t\tLiteGraph.INPUT,\n\t\t\t\ttarget_slot,\n\t\t\t\ttrue,\n\t\t\t\tlink_info,\n\t\t\t\tinput\n\t\t\t);\n\t\t}\n\t\tif (this.graph && this.graph.onNodeConnectionChange) {\n\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\tLiteGraph.INPUT,\n\t\t\t\ttarget_node,\n\t\t\t\ttarget_slot,\n\t\t\t\tthis,\n\t\t\t\tslot\n\t\t\t);\n\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\tthis,\n\t\t\t\tslot,\n\t\t\t\ttarget_node,\n\t\t\t\ttarget_slot\n\t\t\t);\n\t\t}\n\n        this.setDirtyCanvas(false, true);\n\t\tthis.graph.afterChange();\n\t\tthis.graph.connectionChange(this, link_info);\n\n        return link_info;\n    };\n\n    /**\n     * disconnect one output to an specific node\n     * @method disconnectOutput\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]\n     * @return {boolean} if it was disconnected successfully\n     */\n    LGraphNode.prototype.disconnectOutput = function(slot, target_node) {\n        if (slot.constructor === String) {\n            slot = this.findOutputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return false;\n            }\n        } else if (!this.outputs || slot >= this.outputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return false;\n        }\n\n        //get output slot\n        var output = this.outputs[slot];\n        if (!output || !output.links || output.links.length == 0) {\n            return false;\n        }\n\n        //one of the output links in this slot\n        if (target_node) {\n            if (target_node.constructor === Number) {\n                target_node = this.graph.getNodeById(target_node);\n            }\n            if (!target_node) {\n                throw \"Target Node not found\";\n            }\n\n            for (var i = 0, l = output.links.length; i < l; i++) {\n                var link_id = output.links[i];\n                var link_info = this.graph.links[link_id];\n\n                //is the link we are searching for...\n                if (link_info.target_id == target_node.id) {\n                    output.links.splice(i, 1); //remove here\n                    var input = target_node.inputs[link_info.target_slot];\n                    input.link = null; //remove there\n                    delete this.graph.links[link_id]; //remove the link from the links pool\n                    if (this.graph) {\n                        this.graph._version++;\n                    }\n                    if (target_node.onConnectionsChange) {\n                        target_node.onConnectionsChange(\n                            LiteGraph.INPUT,\n                            link_info.target_slot,\n                            false,\n                            link_info,\n                            input\n                        );\n                    } //link_info hasn't been modified so its ok\n                    if (this.onConnectionsChange) {\n                        this.onConnectionsChange(\n                            LiteGraph.OUTPUT,\n                            slot,\n                            false,\n                            link_info,\n                            output\n                        );\n                    }\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.OUTPUT,\n                            this,\n                            slot\n                        );\n                    }\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.OUTPUT,\n                            this,\n                            slot\n                        );\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.INPUT,\n                            target_node,\n                            link_info.target_slot\n                        );\n                    }\n                    break;\n                }\n            }\n        } //all the links in this output slot\n        else {\n            for (var i = 0, l = output.links.length; i < l; i++) {\n                var link_id = output.links[i];\n                var link_info = this.graph.links[link_id];\n                if (!link_info) {\n                    //bug: it happens sometimes\n                    continue;\n                }\n\n                var target_node = this.graph.getNodeById(link_info.target_id);\n                var input = null;\n                if (this.graph) {\n                    this.graph._version++;\n                }\n                if (target_node) {\n                    input = target_node.inputs[link_info.target_slot];\n                    input.link = null; //remove other side link\n                    if (target_node.onConnectionsChange) {\n                        target_node.onConnectionsChange(\n                            LiteGraph.INPUT,\n                            link_info.target_slot,\n                            false,\n                            link_info,\n                            input\n                        );\n                    } //link_info hasn't been modified so its ok\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.INPUT,\n                            target_node,\n                            link_info.target_slot\n                        );\n                    }\n                }\n                delete this.graph.links[link_id]; //remove the link from the links pool\n                if (this.onConnectionsChange) {\n                    this.onConnectionsChange(\n                        LiteGraph.OUTPUT,\n                        slot,\n                        false,\n                        link_info,\n                        output\n                    );\n                }\n                if (this.graph && this.graph.onNodeConnectionChange) {\n                    this.graph.onNodeConnectionChange(\n                        LiteGraph.OUTPUT,\n                        this,\n                        slot\n                    );\n                    this.graph.onNodeConnectionChange(\n                        LiteGraph.INPUT,\n                        target_node,\n                        link_info.target_slot\n                    );\n                }\n            }\n            output.links = null;\n        }\n\n        this.setDirtyCanvas(false, true);\n        this.graph.connectionChange(this);\n        return true;\n    };\n\n    /**\n     * disconnect one input\n     * @method disconnectInput\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @return {boolean} if it was disconnected successfully\n     */\n    LGraphNode.prototype.disconnectInput = function(slot) {\n        //seek for the output slot\n        if (slot.constructor === String) {\n            slot = this.findInputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return false;\n            }\n        } else if (!this.inputs || slot >= this.inputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return false;\n        }\n\n        var input = this.inputs[slot];\n        if (!input) {\n            return false;\n        }\n\n        var link_id = this.inputs[slot].link;\n\t\tif(link_id != null)\n\t\t{\n\t\t\tthis.inputs[slot].link = null;\n\n\t\t\t//remove other side\n\t\t\tvar link_info = this.graph.links[link_id];\n\t\t\tif (link_info) {\n\t\t\t\tvar target_node = this.graph.getNodeById(link_info.origin_id);\n\t\t\t\tif (!target_node) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tvar output = target_node.outputs[link_info.origin_slot];\n\t\t\t\tif (!output || !output.links || output.links.length == 0) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t//search in the inputs list for this link\n\t\t\t\tfor (var i = 0, l = output.links.length; i < l; i++) {\n\t\t\t\t\tif (output.links[i] == link_id) {\n\t\t\t\t\t\toutput.links.splice(i, 1);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tdelete this.graph.links[link_id]; //remove from the pool\n\t\t\t\tif (this.graph) {\n\t\t\t\t\tthis.graph._version++;\n\t\t\t\t}\n\t\t\t\tif (this.onConnectionsChange) {\n\t\t\t\t\tthis.onConnectionsChange(\n\t\t\t\t\t\tLiteGraph.INPUT,\n\t\t\t\t\t\tslot,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tlink_info,\n\t\t\t\t\t\tinput\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (target_node.onConnectionsChange) {\n\t\t\t\t\ttarget_node.onConnectionsChange(\n\t\t\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\t\t\ti,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tlink_info,\n\t\t\t\t\t\toutput\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (this.graph && this.graph.onNodeConnectionChange) {\n\t\t\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\t\t\ttarget_node,\n\t\t\t\t\t\ti\n\t\t\t\t\t);\n\t\t\t\t\tthis.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot);\n\t\t\t\t}\n\t\t\t}\n\t\t} //link != null\n\n        this.setDirtyCanvas(false, true);\n\t\tif(this.graph)\n\t        this.graph.connectionChange(this);\n        return true;\n    };\n\n    /**\n     * returns the center of a connection point in canvas coords\n     * @method getConnectionPos\n     * @param {boolean} is_input true if if a input slot, false if it is an output\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {vec2} out [optional] a place to store the output, to free garbage\n     * @return {[x,y]} the position\n     **/\n    LGraphNode.prototype.getConnectionPos = function(\n        is_input,\n        slot_number,\n        out\n    ) {\n        out = out || new Float32Array(2);\n        var num_slots = 0;\n        if (is_input && this.inputs) {\n            num_slots = this.inputs.length;\n        }\n        if (!is_input && this.outputs) {\n            num_slots = this.outputs.length;\n        }\n\n        var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n\n        if (this.flags.collapsed) {\n            var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;\n            if (this.horizontal) {\n                out[0] = this.pos[0] + w * 0.5;\n                if (is_input) {\n                    out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n                } else {\n                    out[1] = this.pos[1];\n                }\n            } else {\n                if (is_input) {\n                    out[0] = this.pos[0];\n                } else {\n                    out[0] = this.pos[0] + w;\n                }\n                out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            }\n            return out;\n        }\n\n        //weird feature that never got finished\n        if (is_input && slot_number == -1) {\n            out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            return out;\n        }\n\n        //hard-coded pos\n        if (\n            is_input &&\n            num_slots > slot_number &&\n            this.inputs[slot_number].pos\n        ) {\n            out[0] = this.pos[0] + this.inputs[slot_number].pos[0];\n            out[1] = this.pos[1] + this.inputs[slot_number].pos[1];\n            return out;\n        } else if (\n            !is_input &&\n            num_slots > slot_number &&\n            this.outputs[slot_number].pos\n        ) {\n            out[0] = this.pos[0] + this.outputs[slot_number].pos[0];\n            out[1] = this.pos[1] + this.outputs[slot_number].pos[1];\n            return out;\n        }\n\n        //horizontal distributed slots\n        if (this.horizontal) {\n            out[0] =\n                this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots);\n            if (is_input) {\n                out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n            } else {\n                out[1] = this.pos[1] + this.size[1];\n            }\n            return out;\n        }\n\n        //default vertical slots\n        if (is_input) {\n            out[0] = this.pos[0] + offset;\n        } else {\n            out[0] = this.pos[0] + this.size[0] + 1 - offset;\n        }\n        out[1] =\n            this.pos[1] +\n            (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT +\n            (this.constructor.slot_start_y || 0);\n        return out;\n    };\n\n    /* Force align to grid */\n    LGraphNode.prototype.alignToGrid = function() {\n        this.pos[0] =\n            LiteGraph.CANVAS_GRID_SIZE *\n            Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE);\n        this.pos[1] =\n            LiteGraph.CANVAS_GRID_SIZE *\n            Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE);\n    };\n\n    /* Console output */\n    LGraphNode.prototype.trace = function(msg) {\n        if (!this.console) {\n            this.console = [];\n        }\n\n        this.console.push(msg);\n        if (this.console.length > LGraphNode.MAX_CONSOLE) {\n            this.console.shift();\n        }\n\n\t\tif(this.graph.onNodeTrace)\n\t        this.graph.onNodeTrace(this, msg);\n    };\n\n    /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */\n    LGraphNode.prototype.setDirtyCanvas = function(\n        dirty_foreground,\n        dirty_background\n    ) {\n        if (!this.graph) {\n            return;\n        }\n        this.graph.sendActionToCanvas(\"setDirty\", [\n            dirty_foreground,\n            dirty_background\n        ]);\n    };\n\n    LGraphNode.prototype.loadImage = function(url) {\n        var img = new Image();\n        img.src = LiteGraph.node_images_path + url;\n        img.ready = false;\n\n        var that = this;\n        img.onload = function() {\n            this.ready = true;\n            that.setDirtyCanvas(true);\n        };\n        return img;\n    };\n\n    //safe LGraphNode action execution (not sure if safe)\n    /*\nLGraphNode.prototype.executeAction = function(action)\n{\n\tif(action == \"\") return false;\n\n\tif( action.indexOf(\";\") != -1 || action.indexOf(\"}\") != -1)\n\t{\n\t\tthis.trace(\"Error: Action contains unsafe characters\");\n\t\treturn false;\n\t}\n\n\tvar tokens = action.split(\"(\");\n\tvar func_name = tokens[0];\n\tif( typeof(this[func_name]) != \"function\")\n\t{\n\t\tthis.trace(\"Error: Action not found on node: \" + func_name);\n\t\treturn false;\n\t}\n\n\tvar code = action;\n\n\ttry\n\t{\n\t\tvar _foo = eval;\n\t\teval = null;\n\t\t(new Function(\"with(this) { \" + code + \"}\")).call(this);\n\t\teval = _foo;\n\t}\n\tcatch (err)\n\t{\n\t\tthis.trace(\"Error executing action {\" + action + \"} :\" + err);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n*/\n\n    /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */\n    LGraphNode.prototype.captureInput = function(v) {\n        if (!this.graph || !this.graph.list_of_graphcanvas) {\n            return;\n        }\n\n        var list = this.graph.list_of_graphcanvas;\n\n        for (var i = 0; i < list.length; ++i) {\n            var c = list[i];\n            //releasing somebody elses capture?!\n            if (!v && c.node_capturing_input != this) {\n                continue;\n            }\n\n            //change\n            c.node_capturing_input = v ? this : null;\n        }\n    };\n\n    /**\n     * Collapse the node to make it smaller on the canvas\n     * @method collapse\n     **/\n    LGraphNode.prototype.collapse = function(force) {\n        this.graph._version++;\n        if (this.constructor.collapsable === false && !force) {\n            return;\n        }\n        if (!this.flags.collapsed) {\n            this.flags.collapsed = true;\n        } else {\n            this.flags.collapsed = false;\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * Forces the node to do not move or realign on Z\n     * @method pin\n     **/\n\n    LGraphNode.prototype.pin = function(v) {\n        this.graph._version++;\n        if (v === undefined) {\n            this.flags.pinned = !this.flags.pinned;\n        } else {\n            this.flags.pinned = v;\n        }\n    };\n\n    LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) {\n        return [\n            (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0],\n            (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]\n        ];\n    };\n\n    function LGraphGroup(title) {\n        this._ctor(title);\n    }\n\n    global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup;\n\n    LGraphGroup.prototype._ctor = function(title) {\n        this.title = title || \"Group\";\n        this.font_size = 24;\n        this.color = LGraphCanvas.node_colors.pale_blue\n            ? LGraphCanvas.node_colors.pale_blue.groupcolor\n            : \"#AAA\";\n        this._bounding = new Float32Array([10, 10, 140, 80]);\n        this._pos = this._bounding.subarray(0, 2);\n        this._size = this._bounding.subarray(2, 4);\n        this._nodes = [];\n        this.graph = null;\n\n        Object.defineProperty(this, \"pos\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._pos[0] = v[0];\n                this._pos[1] = v[1];\n            },\n            get: function() {\n                return this._pos;\n            },\n            enumerable: true\n        });\n\n        Object.defineProperty(this, \"size\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._size[0] = Math.max(140, v[0]);\n                this._size[1] = Math.max(80, v[1]);\n            },\n            get: function() {\n                return this._size;\n            },\n            enumerable: true\n        });\n    };\n\n    LGraphGroup.prototype.configure = function(o) {\n        this.title = o.title;\n        this._bounding.set(o.bounding);\n        this.color = o.color;\n        this.font_size = o.font_size;\n    };\n\n    LGraphGroup.prototype.serialize = function() {\n        var b = this._bounding;\n        return {\n            title: this.title,\n            bounding: [\n                Math.round(b[0]),\n                Math.round(b[1]),\n                Math.round(b[2]),\n                Math.round(b[3])\n            ],\n            color: this.color,\n            font_size: this.font_size\n        };\n    };\n\n    LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) {\n        this._pos[0] += deltax;\n        this._pos[1] += deltay;\n        if (ignore_nodes) {\n            return;\n        }\n        for (var i = 0; i < this._nodes.length; ++i) {\n            var node = this._nodes[i];\n            node.pos[0] += deltax;\n            node.pos[1] += deltay;\n        }\n    };\n\n    LGraphGroup.prototype.recomputeInsideNodes = function() {\n        this._nodes.length = 0;\n        var nodes = this.graph._nodes;\n        var node_bounding = new Float32Array(4);\n\n        for (var i = 0; i < nodes.length; ++i) {\n            var node = nodes[i];\n            node.getBounding(node_bounding);\n            if (!overlapBounding(this._bounding, node_bounding)) {\n                continue;\n            } //out of the visible area\n            this._nodes.push(node);\n        }\n    };\n\n    LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside;\n    LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas;\n\n    //****************************************\n\n    //Scale and Offset\n    function DragAndScale(element, skip_events) {\n        this.offset = new Float32Array([0, 0]);\n        this.scale = 1;\n        this.max_scale = 10;\n        this.min_scale = 0.1;\n        this.onredraw = null;\n        this.enabled = true;\n        this.last_mouse = [0, 0];\n        this.element = null;\n        this.visible_area = new Float32Array(4);\n\n        if (element) {\n            this.element = element;\n            if (!skip_events) {\n                this.bindEvents(element);\n            }\n        }\n    }\n\n    LiteGraph.DragAndScale = DragAndScale;\n\n    DragAndScale.prototype.bindEvents = function(element) {\n        this.last_mouse = new Float32Array(2);\n\n        this._binded_mouse_callback = this.onMouse.bind(this);\n\n\t\tLiteGraph.pointerListenerAdd(element,\"down\", this._binded_mouse_callback);\n\t\tLiteGraph.pointerListenerAdd(element,\"move\", this._binded_mouse_callback);\n\t\tLiteGraph.pointerListenerAdd(element,\"up\", this._binded_mouse_callback);\n\n        element.addEventListener(\n            \"mousewheel\",\n            this._binded_mouse_callback,\n            false\n        );\n        element.addEventListener(\"wheel\", this._binded_mouse_callback, false);\n    };\n\n    DragAndScale.prototype.computeVisibleArea = function( viewport ) {\n        if (!this.element) {\n            this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0;\n            return;\n        }\n        var width = this.element.width;\n        var height = this.element.height;\n        var startx = -this.offset[0];\n        var starty = -this.offset[1];\n\t\tif( viewport )\n\t\t{\n\t\t\tstartx += viewport[0] / this.scale;\n\t\t\tstarty += viewport[1] / this.scale;\n\t\t\twidth = viewport[2];\n\t\t\theight = viewport[3];\n\t\t}\n        var endx = startx + width / this.scale;\n        var endy = starty + height / this.scale;\n        this.visible_area[0] = startx;\n        this.visible_area[1] = starty;\n        this.visible_area[2] = endx - startx;\n        this.visible_area[3] = endy - starty;\n    };\n\n    DragAndScale.prototype.onMouse = function(e) {\n        if (!this.enabled) {\n            return;\n        }\n\n        var canvas = this.element;\n        var rect = canvas.getBoundingClientRect();\n        var x = e.clientX - rect.left;\n        var y = e.clientY - rect.top;\n        e.canvasx = x;\n        e.canvasy = y;\n        e.dragging = this.dragging;\n        \n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\n\t\t//console.log(\"pointerevents: DragAndScale onMouse \"+e.type+\" \"+is_inside);\n\t\t\n        var ignore = false;\n        if (this.onmouse) {\n            ignore = this.onmouse(e);\n        }\n\n        if (e.type == LiteGraph.pointerevents_method+\"down\" && is_inside) {\n            this.dragging = true;\n\t\t\tLiteGraph.pointerListenerRemove(canvas,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(document,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(document,\"up\",this._binded_mouse_callback);\n        } else if (e.type == LiteGraph.pointerevents_method+\"move\") {\n            if (!ignore) {\n                var deltax = x - this.last_mouse[0];\n                var deltay = y - this.last_mouse[1];\n                if (this.dragging) {\n                    this.mouseDrag(deltax, deltay);\n                }\n            }\n        } else if (e.type == LiteGraph.pointerevents_method+\"up\") {\n            this.dragging = false;\n\t\t\tLiteGraph.pointerListenerRemove(document,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerRemove(document,\"up\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(canvas,\"move\",this._binded_mouse_callback);\n        } else if ( is_inside &&\n            (e.type == \"mousewheel\" ||\n            e.type == \"wheel\" ||\n            e.type == \"DOMMouseScroll\")\n        ) {\n            e.eventType = \"mousewheel\";\n            if (e.type == \"wheel\") {\n                e.wheel = -e.deltaY;\n            } else {\n                e.wheel =\n                    e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60;\n            }\n\n            //from stack overflow\n            e.delta = e.wheelDelta\n                ? e.wheelDelta / 40\n                : e.deltaY\n                ? -e.deltaY / 3\n                : 0;\n            this.changeDeltaScale(1.0 + e.delta * 0.05);\n        }\n\n        this.last_mouse[0] = x;\n        this.last_mouse[1] = y;\n\n\t\tif(is_inside)\n\t\t{\n\t        e.preventDefault();\n\t\t    e.stopPropagation();\n\t\t    return false;\n\t\t}\n    };\n\n    DragAndScale.prototype.toCanvasContext = function(ctx) {\n        ctx.scale(this.scale, this.scale);\n        ctx.translate(this.offset[0], this.offset[1]);\n    };\n\n    DragAndScale.prototype.convertOffsetToCanvas = function(pos) {\n        //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]];\n        return [\n            (pos[0] + this.offset[0]) * this.scale,\n            (pos[1] + this.offset[1]) * this.scale\n        ];\n    };\n\n    DragAndScale.prototype.convertCanvasToOffset = function(pos, out) {\n        out = out || [0, 0];\n        out[0] = pos[0] / this.scale - this.offset[0];\n        out[1] = pos[1] / this.scale - this.offset[1];\n        return out;\n    };\n\n    DragAndScale.prototype.mouseDrag = function(x, y) {\n        this.offset[0] += x / this.scale;\n        this.offset[1] += y / this.scale;\n\n        if (this.onredraw) {\n            this.onredraw(this);\n        }\n    };\n\n    DragAndScale.prototype.changeScale = function(value, zooming_center) {\n        if (value < this.min_scale) {\n            value = this.min_scale;\n        } else if (value > this.max_scale) {\n            value = this.max_scale;\n        }\n\n        if (value == this.scale) {\n            return;\n        }\n\n        if (!this.element) {\n            return;\n        }\n\n        var rect = this.element.getBoundingClientRect();\n        if (!rect) {\n            return;\n        }\n\n        zooming_center = zooming_center || [\n            rect.width * 0.5,\n            rect.height * 0.5\n        ];\n        var center = this.convertCanvasToOffset(zooming_center);\n        this.scale = value;\n        if (Math.abs(this.scale - 1) < 0.01) {\n            this.scale = 1;\n        }\n\n        var new_center = this.convertCanvasToOffset(zooming_center);\n        var delta_offset = [\n            new_center[0] - center[0],\n            new_center[1] - center[1]\n        ];\n\n        this.offset[0] += delta_offset[0];\n        this.offset[1] += delta_offset[1];\n\n        if (this.onredraw) {\n            this.onredraw(this);\n        }\n    };\n\n    DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) {\n        this.changeScale(this.scale * value, zooming_center);\n    };\n\n    DragAndScale.prototype.reset = function() {\n        this.scale = 1;\n        this.offset[0] = 0;\n        this.offset[1] = 0;\n    };\n\n    //*********************************************************************************\n    // LGraphCanvas: LGraph renderer CLASS\n    //*********************************************************************************\n\n    /**\n     * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required.\n     * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked\n     *\n     * @class LGraphCanvas\n     * @constructor\n     * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself)\n     * @param {LGraph} graph [optional]\n     * @param {Object} options [optional] { skip_rendering, autoresize, viewport }\n     */\n    function LGraphCanvas(canvas, graph, options) {\n        this.options = options = options || {};\n\n        //if(graph === undefined)\n        //\tthrow (\"No graph assigned\");\n        this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE;\n\n        if (canvas && canvas.constructor === String) {\n            canvas = document.querySelector(canvas);\n        }\n\n        this.ds = new DragAndScale();\n        this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much\n\n        this.title_text_font = \"\" + LiteGraph.NODE_TEXT_SIZE + \"px Arial\";\n        this.inner_text_font =\n            \"normal \" + LiteGraph.NODE_SUBTEXT_SIZE + \"px Arial\";\n        this.node_title_color = LiteGraph.NODE_TITLE_COLOR;\n        this.default_link_color = LiteGraph.LINK_COLOR;\n        this.default_connection_color = {\n            input_off: \"#778\",\n            input_on: \"#7F7\", //\"#BBD\"\n            output_off: \"#778\",\n            output_on: \"#7F7\" //\"#BBD\"\n\t\t};\n        this.default_connection_color_byType = {\n            /*number: \"#7F7\",\n            string: \"#77F\",\n            boolean: \"#F77\",*/\n        }\n        this.default_connection_color_byTypeOff = {\n            /*number: \"#474\",\n            string: \"#447\",\n            boolean: \"#744\",*/\n        };\n\n        this.highquality_render = true;\n        this.use_gradients = false; //set to true to render titlebar with gradients\n        this.editor_alpha = 1; //used for transition\n        this.pause_rendering = false;\n        this.clear_background = true;\n        this.clear_background_color = \"#222\";\n\n\t\tthis.read_only = false; //if set to true users cannot modify the graph\n        this.render_only_selected = true;\n        this.live_mode = false;\n        this.show_info = true;\n        this.allow_dragcanvas = true;\n        this.allow_dragnodes = true;\n        this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc\n        this.multi_select = false; //allow selecting multi nodes without pressing extra keys\n        this.allow_searchbox = true;\n        this.allow_reconnect_links = true; //allows to change a connection with having to redo it again\n\t\tthis.align_to_grid = false; //snap to grid\n\n        this.drag_mode = false;\n        this.dragging_rectangle = null;\n\n        this.filter = null; //allows to filter to only accept some type of nodes in a graph\n\n\t\tthis.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything\n        this.always_render_background = false;\n        this.render_shadows = true;\n        this.render_canvas_border = true;\n        this.render_connections_shadows = false; //too much cpu\n        this.render_connections_border = true;\n        this.render_curved_connections = false;\n        this.render_connection_arrows = false;\n        this.render_collapsed_slots = true;\n        this.render_execution_order = false;\n        this.render_title_colored = true;\n\t\tthis.render_link_tooltip = true;\n\n        this.links_render_mode = LiteGraph.SPLINE_LINK;\n\n        this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle\n        this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle\n\t\tthis.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD\n\n        //to personalize the search box\n        this.onSearchBox = null;\n        this.onSearchBoxSelection = null;\n\n        //callbacks\n        this.onMouse = null;\n        this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform\n        this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform\n        this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs)\n\t\tthis.onDrawLinkTooltip = null; //called when rendering a tooltip\n\t\tthis.onNodeMoved = null; //called after moving a node\n\t\tthis.onSelectionChange = null; //called if the selection changes\n\t\tthis.onConnectingChange = null; //called before any link changes\n\t\tthis.onBeforeChange = null; //called before modifying the graph\n\t\tthis.onAfterChange = null; //called after modifying the graph\n\n        this.connections_width = 3;\n        this.round_radius = 8;\n\n        this.current_node = null;\n        this.node_widget = null; //used for widgets\n\t\tthis.over_link_center = null;\n        this.last_mouse_position = [0, 0];\n        this.visible_area = this.ds.visible_area;\n        this.visible_links = [];\n\n\t\tthis.viewport = options.viewport || null; //to constraint render area to a portion of the canvas\n\n        //link canvas and graph\n        if (graph) {\n            graph.attachCanvas(this);\n        }\n\n        this.setCanvas(canvas,options.skip_events);\n        this.clear();\n\n        if (!options.skip_render) {\n            this.startRendering();\n        }\n\n        this.autoresize = options.autoresize;\n    }\n\n    global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas;\n\n\tLGraphCanvas.DEFAULT_BACKGROUND_IMAGE = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=\";\n\n    LGraphCanvas.link_type_colors = {\n        \"-1\": LiteGraph.EVENT_LINK_COLOR,\n        number: \"#AAA\",\n        node: \"#DCA\"\n    };\n    LGraphCanvas.gradients = {}; //cache of gradients\n\n    /**\n     * clears all the data inside\n     *\n     * @method clear\n     */\n    LGraphCanvas.prototype.clear = function() {\n        this.frame = 0;\n        this.last_draw_time = 0;\n        this.render_time = 0;\n        this.fps = 0;\n\n        //this.scale = 1;\n        //this.offset = [0,0];\n\n        this.dragging_rectangle = null;\n\n        this.selected_nodes = {};\n        this.selected_group = null;\n\n        this.visible_nodes = [];\n        this.node_dragged = null;\n        this.node_over = null;\n        this.node_capturing_input = null;\n        this.connecting_node = null;\n        this.highlighted_links = {};\n\n\t\tthis.dragging_canvas = false;\n\n        this.dirty_canvas = true;\n        this.dirty_bgcanvas = true;\n        this.dirty_area = null;\n\n        this.node_in_panel = null;\n        this.node_widget = null;\n\n        this.last_mouse = [0, 0];\n        this.last_mouseclick = 0;\n\t  \tthis.pointer_is_down = false;\n\t  \tthis.pointer_is_double = false;\n        this.visible_area.set([0, 0, 0, 0]);\n\n        if (this.onClear) {\n            this.onClear();\n        }\n    };\n\n    /**\n     * assigns a graph, you can reassign graphs to the same canvas\n     *\n     * @method setGraph\n     * @param {LGraph} graph\n     */\n    LGraphCanvas.prototype.setGraph = function(graph, skip_clear) {\n        if (this.graph == graph) {\n            return;\n        }\n\n        if (!skip_clear) {\n            this.clear();\n        }\n\n        if (!graph && this.graph) {\n            this.graph.detachCanvas(this);\n            return;\n        }\n\n        graph.attachCanvas(this);\n\n\t\t//remove the graph stack in case a subgraph was open\n\t\tif (this._graph_stack)\n\t\t\tthis._graph_stack = null;\n\n        this.setDirty(true, true);\n    };\n\n    /**\n     * returns the top level graph (in case there are subgraphs open on the canvas)\n     *\n     * @method getTopGraph\n     * @return {LGraph} graph\n     */\n\tLGraphCanvas.prototype.getTopGraph = function()\n\t{\n\t\tif(this._graph_stack.length)\n\t\t\treturn this._graph_stack[0];\n\t\treturn this.graph;\n\t}\n\n    /**\n     * opens a graph contained inside a node in the current graph\n     *\n     * @method openSubgraph\n     * @param {LGraph} graph\n     */\n    LGraphCanvas.prototype.openSubgraph = function(graph) {\n        if (!graph) {\n            throw \"graph cannot be null\";\n        }\n\n        if (this.graph == graph) {\n            throw \"graph cannot be the same\";\n        }\n\n        this.clear();\n\n        if (this.graph) {\n            if (!this._graph_stack) {\n                this._graph_stack = [];\n            }\n            this._graph_stack.push(this.graph);\n        }\n\n        graph.attachCanvas(this);\n\t\tthis.checkPanels();\n        this.setDirty(true, true);\n    };\n\n    /**\n     * closes a subgraph contained inside a node\n     *\n     * @method closeSubgraph\n     * @param {LGraph} assigns a graph\n     */\n    LGraphCanvas.prototype.closeSubgraph = function() {\n        if (!this._graph_stack || this._graph_stack.length == 0) {\n            return;\n        }\n        var subgraph_node = this.graph._subgraph_node;\n        var graph = this._graph_stack.pop();\n        this.selected_nodes = {};\n        this.highlighted_links = {};\n        graph.attachCanvas(this);\n        this.setDirty(true, true);\n        if (subgraph_node) {\n            this.centerOnNode(subgraph_node);\n            this.selectNodes([subgraph_node]);\n        }\n        // when close sub graph back to offset [0, 0] scale 1\n        this.ds.offset = [0, 0]\n        this.ds.scale = 1\n    };\n\n    /**\n     * returns the visually active graph (in case there are more in the stack)\n     * @method getCurrentGraph\n     * @return {LGraph} the active graph\n     */\n    LGraphCanvas.prototype.getCurrentGraph = function() {\n        return this.graph;\n    };\n\n    /**\n     * assigns a canvas\n     *\n     * @method setCanvas\n     * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector)\n     */\n    LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) {\n        var that = this;\n\n        if (canvas) {\n            if (canvas.constructor === String) {\n                canvas = document.getElementById(canvas);\n                if (!canvas) {\n                    throw \"Error creating LiteGraph canvas: Canvas not found\";\n                }\n            }\n        }\n\n        if (canvas === this.canvas) {\n            return;\n        }\n\n        if (!canvas && this.canvas) {\n            //maybe detach events from old_canvas\n            if (!skip_events) {\n                this.unbindEvents();\n            }\n        }\n\n        this.canvas = canvas;\n        this.ds.element = canvas;\n\n        if (!canvas) {\n            return;\n        }\n\n        //this.canvas.tabindex = \"1000\";\n        canvas.className += \" lgraphcanvas\";\n        canvas.data = this;\n        canvas.tabindex = \"1\"; //to allow key events\n\n        //bg canvas: used for non changing stuff\n        this.bgcanvas = null;\n        if (!this.bgcanvas) {\n            this.bgcanvas = document.createElement(\"canvas\");\n            this.bgcanvas.width = this.canvas.width;\n            this.bgcanvas.height = this.canvas.height;\n        }\n\n        if (canvas.getContext == null) {\n            if (canvas.localName != \"canvas\") {\n                throw \"Element supplied for LGraphCanvas must be a <canvas> element, you passed a \" +\n                    canvas.localName;\n            }\n            throw \"This browser doesn't support Canvas\";\n        }\n\n        var ctx = (this.ctx = canvas.getContext(\"2d\"));\n        if (ctx == null) {\n            if (!canvas.webgl_enabled) {\n                console.warn(\n                    \"This canvas seems to be WebGL, enabling WebGL renderer\"\n                );\n            }\n            this.enableWebGL();\n        }\n\n        //input:  (move and up could be unbinded)\n        // why here? this._mousemove_callback = this.processMouseMove.bind(this);\n        // why here? this._mouseup_callback = this.processMouseUp.bind(this);\n\n        if (!skip_events) {\n            this.bindEvents();\n        }\n    };\n\n    //used in some events to capture them\n    LGraphCanvas.prototype._doNothing = function doNothing(e) {\n    \t//console.log(\"pointerevents: _doNothing \"+e.type);\n        e.preventDefault();\n        return false;\n    };\n    LGraphCanvas.prototype._doReturnTrue = function doNothing(e) {\n        e.preventDefault();\n        return true;\n    };\n\n    /**\n     * binds mouse, keyboard, touch and drag events to the canvas\n     * @method bindEvents\n     **/\n    LGraphCanvas.prototype.bindEvents = function() {\n        if (this._events_binded) {\n            console.warn(\"LGraphCanvas: events already binded\");\n            return;\n        }\n\n        //console.log(\"pointerevents: bindEvents\");\n        \n        var canvas = this.canvas;\n\n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document; //hack used when moving canvas between windows\n\n        this._mousedown_callback = this.processMouseDown.bind(this);\n        this._mousewheel_callback = this.processMouseWheel.bind(this);\n        // why mousemove and mouseup were not binded here?\n        this._mousemove_callback = this.processMouseMove.bind(this);\n        this._mouseup_callback = this.processMouseUp.bind(this);\n        \n        //touch events -- TODO IMPLEMENT\n        //this._touch_callback = this.touchHandler.bind(this);\n\n\t\tLiteGraph.pointerListenerAdd(canvas,\"down\", this._mousedown_callback, true); //down do not need to store the binded\n        canvas.addEventListener(\"mousewheel\", this._mousewheel_callback, false);\n\n        LiteGraph.pointerListenerAdd(canvas,\"up\", this._mouseup_callback, true); // CHECK: ??? binded or not\n\t\tLiteGraph.pointerListenerAdd(canvas,\"move\", this._mousemove_callback);\n        \n        canvas.addEventListener(\"contextmenu\", this._doNothing);\n        canvas.addEventListener(\n            \"DOMMouseScroll\",\n            this._mousewheel_callback,\n            false\n        );\n\n        //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents\n        /*if( 'touchstart' in document.documentElement )\n        {\n            canvas.addEventListener(\"touchstart\", this._touch_callback, true);\n            canvas.addEventListener(\"touchmove\", this._touch_callback, true);\n            canvas.addEventListener(\"touchend\", this._touch_callback, true);\n            canvas.addEventListener(\"touchcancel\", this._touch_callback, true);\n        }*/\n\n        //Keyboard ******************\n        this._key_callback = this.processKey.bind(this);\n        canvas.setAttribute(\"tabindex\",1); //otherwise key events are ignored\n        canvas.addEventListener(\"keydown\", this._key_callback, true);\n        document.addEventListener(\"keyup\", this._key_callback, true); //in document, otherwise it doesn't fire keyup\n\n        //Dropping Stuff over nodes ************************************\n        this._ondrop_callback = this.processDrop.bind(this);\n\n        canvas.addEventListener(\"dragover\", this._doNothing, false);\n        canvas.addEventListener(\"dragend\", this._doNothing, false);\n        canvas.addEventListener(\"drop\", this._ondrop_callback, false);\n        canvas.addEventListener(\"dragenter\", this._doReturnTrue, false);\n\n        this._events_binded = true;\n    };\n\n    /**\n     * unbinds mouse events from the canvas\n     * @method unbindEvents\n     **/\n    LGraphCanvas.prototype.unbindEvents = function() {\n        if (!this._events_binded) {\n            console.warn(\"LGraphCanvas: no events binded\");\n            return;\n        }\n\n        //console.log(\"pointerevents: unbindEvents\");\n        \n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document;\n\n\t\tLiteGraph.pointerListenerRemove(this.canvas,\"move\", this._mousedown_callback);\n        LiteGraph.pointerListenerRemove(this.canvas,\"up\", this._mousedown_callback);\n        LiteGraph.pointerListenerRemove(this.canvas,\"down\", this._mousedown_callback);\n        this.canvas.removeEventListener(\n            \"mousewheel\",\n            this._mousewheel_callback\n        );\n        this.canvas.removeEventListener(\n            \"DOMMouseScroll\",\n            this._mousewheel_callback\n        );\n        this.canvas.removeEventListener(\"keydown\", this._key_callback);\n        document.removeEventListener(\"keyup\", this._key_callback);\n        this.canvas.removeEventListener(\"contextmenu\", this._doNothing);\n        this.canvas.removeEventListener(\"drop\", this._ondrop_callback);\n        this.canvas.removeEventListener(\"dragenter\", this._doReturnTrue);\n\n        //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents\n        /*this.canvas.removeEventListener(\"touchstart\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchmove\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchend\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchcancel\", this._touch_callback );*/\n\n        this._mousedown_callback = null;\n        this._mousewheel_callback = null;\n        this._key_callback = null;\n        this._ondrop_callback = null;\n\n        this._events_binded = false;\n    };\n\n    LGraphCanvas.getFileExtension = function(url) {\n        var question = url.indexOf(\"?\");\n        if (question != -1) {\n            url = url.substr(0, question);\n        }\n        var point = url.lastIndexOf(\".\");\n        if (point == -1) {\n            return \"\";\n        }\n        return url.substr(point + 1).toLowerCase();\n    };\n\n    /**\n     * this function allows to render the canvas using WebGL instead of Canvas2D\n     * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL\n     * @method enableWebGL\n     **/\n    LGraphCanvas.prototype.enableWebGL = function() {\n        if (typeof GL === \"undefined\") {\n            throw \"litegl.js must be included to use a WebGL canvas\";\n        }\n        if (typeof enableWebGLCanvas === \"undefined\") {\n            throw \"webglCanvas.js must be included to use this feature\";\n        }\n\n        this.gl = this.ctx = enableWebGLCanvas(this.canvas);\n        this.ctx.webgl = true;\n        this.bgcanvas = this.canvas;\n        this.bgctx = this.gl;\n        this.canvas.webgl_enabled = true;\n\n        /*\n\tGL.create({ canvas: this.bgcanvas });\n\tthis.bgctx = enableWebGLCanvas( this.bgcanvas );\n\twindow.gl = this.gl;\n\t*/\n    };\n\n    /**\n     * marks as dirty the canvas, this way it will be rendered again\n     *\n     * @class LGraphCanvas\n     * @method setDirty\n     * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes)\n     * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires)\n     */\n    LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) {\n        if (fgcanvas) {\n            this.dirty_canvas = true;\n        }\n        if (bgcanvas) {\n            this.dirty_bgcanvas = true;\n        }\n    };\n\n    /**\n     * Used to attach the canvas in a popup\n     *\n     * @method getCanvasWindow\n     * @return {window} returns the window where the canvas is attached (the DOM root node)\n     */\n    LGraphCanvas.prototype.getCanvasWindow = function() {\n        if (!this.canvas) {\n            return window;\n        }\n        var doc = this.canvas.ownerDocument;\n        return doc.defaultView || doc.parentWindow;\n    };\n\n    /**\n     * starts rendering the content of the canvas when needed\n     *\n     * @method startRendering\n     */\n    LGraphCanvas.prototype.startRendering = function() {\n        if (this.is_rendering) {\n            return;\n        } //already rendering\n\n        this.is_rendering = true;\n        renderFrame.call(this);\n\n        function renderFrame() {\n            if (!this.pause_rendering) {\n                this.draw();\n            }\n\n            var window = this.getCanvasWindow();\n            if (this.is_rendering) {\n                window.requestAnimationFrame(renderFrame.bind(this));\n            }\n        }\n    };\n\n    /**\n     * stops rendering the content of the canvas (to save resources)\n     *\n     * @method stopRendering\n     */\n    LGraphCanvas.prototype.stopRendering = function() {\n        this.is_rendering = false;\n        /*\n\tif(this.rendering_timer_id)\n\t{\n\t\tclearInterval(this.rendering_timer_id);\n\t\tthis.rendering_timer_id = null;\n\t}\n\t*/\n    };\n\n    /* LiteGraphCanvas input */\n\n\t//used to block future mouse events (because of im gui)\n\tLGraphCanvas.prototype.blockClick = function()\n\t{\n\t\tthis.block_click = true;\n\t\tthis.last_mouseclick = 0;\n\t}\n\t\n    LGraphCanvas.prototype.processMouseDown = function(e) {\n    \t\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\t\t\n\t\tif (!this.graph) {\n            return;\n        }\n\n        this.adjustMouseEvent(e);\n\n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document;\n        LGraphCanvas.active_canvas = this;\n        var that = this;\n\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\t//console.log(y,this.viewport);\n\t\t//console.log(\"pointerevents: processMouseDown pointerId:\"+e.pointerId+\" which:\"+e.which+\" isPrimary:\"+e.isPrimary+\" :: x y \"+x+\" \"+y);\n\n\t\tthis.ds.viewport = this.viewport;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\n        //move mouse move event to the window in case it drags outside of the canvas\n\t\tif(!this.options.skip_events)\n\t\t{\n\t\t\tLiteGraph.pointerListenerRemove(this.canvas,\"move\", this._mousemove_callback);\n\t\t\tLiteGraph.pointerListenerAdd(ref_window.document,\"move\", this._mousemove_callback,true); //catch for the entire window\n\t\t\tLiteGraph.pointerListenerAdd(ref_window.document,\"up\", this._mouseup_callback,true);\n\t\t}\n\n\t\tif(!is_inside){\n\t\t\treturn;\n\t\t}\n\n        var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 );\n        var skip_dragging = false;\n        var skip_action = false;\n        var now = LiteGraph.getTime();\n\t\tvar is_primary = (e.isPrimary === undefined || !e.isPrimary);\n        var is_double_click = (now - this.last_mouseclick < 300) && is_primary;\n\t\tthis.mouse[0] = e.clientX;\n\t\tthis.mouse[1] = e.clientY;\n        this.graph_mouse[0] = e.canvasX;\n        this.graph_mouse[1] = e.canvasY;\n\t\tthis.last_click_position = [this.mouse[0],this.mouse[1]];\n\t  \t\n\t  \tif (this.pointer_is_down && is_primary ){\n\t\t  this.pointer_is_double = true;\n\t\t  //console.log(\"pointerevents: pointer_is_double start\");\n\t\t}else{\n\t\t  this.pointer_is_double = false;\n\t\t}\n\t  \tthis.pointer_is_down = true;\n\t  \n\t  \t\n        this.canvas.focus();\n\n        LiteGraph.closeAllContextMenus(ref_window);\n\n        if (this.onMouse)\n\t\t{\n            if (this.onMouse(e) == true)\n                return;\n        }\n\n\t\t//left button mouse / single finger\n        if (e.which == 1 && !this.pointer_is_double)\n\t\t{\n            if (e.ctrlKey)\n\t\t\t{\n                this.dragging_rectangle = new Float32Array(4);\n                this.dragging_rectangle[0] = e.canvasX;\n                this.dragging_rectangle[1] = e.canvasY;\n                this.dragging_rectangle[2] = 1;\n                this.dragging_rectangle[3] = 1;\n                skip_action = true;\n            }\n\n            // clone node ALT dragging\n            if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && node && this.allow_interaction && !skip_action && !this.read_only)\n            {\n                if (cloned = node.clone()){\n                    cloned.pos[0] += 5;\n                    cloned.pos[1] += 5;\n                    this.graph.add(cloned,false,{doCalcSize: false});\n                    node = cloned;\n                    skip_action = true;\n                    if (!block_drag_node) {\n                        if (this.allow_dragnodes) {\n\t\t\t\t\t\t\tthis.graph.beforeChange();\n                            this.node_dragged = node;\n                        }\n                        if (!this.selected_nodes[node.id]) {\n                            this.processNodeSelected(node, e);\n                        }\n                    }\n                }\n            }\n            \n            var clicking_canvas_bg = false;\n\n            //when clicked on top of a node\n            //and it is not interactive\n            if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) {\n                if (!this.live_mode && !node.flags.pinned) {\n                    this.bringToFront(node);\n                } //if it wasn't selected?\n\n                //not dragging mouse to connect two slots\n                if ( this.allow_interaction && !this.connecting_node && !node.flags.collapsed && !this.live_mode ) {\n                    //Search for corner for resize\n                    if ( !skip_action &&\n                        node.resizable !== false &&\n                        isInsideRectangle( e.canvasX,\n                            e.canvasY,\n                            node.pos[0] + node.size[0] - 5,\n                            node.pos[1] + node.size[1] - 5,\n                            10,\n                            10\n                        )\n                    ) {\n\t\t\t\t\t\tthis.graph.beforeChange();\n                        this.resizing_node = node;\n                        this.canvas.style.cursor = \"se-resize\";\n                        skip_action = true;\n                    } else {\n                        //search for outputs\n                        if (node.outputs) {\n                            for ( var i = 0, l = node.outputs.length; i < l; ++i ) {\n                                var output = node.outputs[i];\n                                var link_pos = node.getConnectionPos(false, i);\n                                if (\n                                    isInsideRectangle(\n                                        e.canvasX,\n                                        e.canvasY,\n                                        link_pos[0] - 15,\n                                        link_pos[1] - 10,\n                                        30,\n                                        20\n                                    )\n                                ) {\n                                    this.connecting_node = node;\n                                    this.connecting_output = output;\n                                    this.connecting_output.slot_index = i;\n                                    this.connecting_pos = node.getConnectionPos( false, i );\n                                    this.connecting_slot = i;\n\n                                    if (LiteGraph.shift_click_do_break_link_from){\n                                        if (e.shiftKey) {\n                                            node.disconnectOutput(i);\n                                        }\n                                    }\n\n                                    if (is_double_click) {\n                                        if (node.onOutputDblClick) {\n                                            node.onOutputDblClick(i, e);\n                                        }\n                                    } else {\n                                        if (node.onOutputClick) {\n                                            node.onOutputClick(i, e);\n                                        }\n                                    }\n\n                                    skip_action = true;\n                                    break;\n                                }\n                            }\n                        }\n\n                        //search for inputs\n                        if (node.inputs) {\n                            for ( var i = 0, l = node.inputs.length; i < l; ++i ) {\n                                var input = node.inputs[i];\n                                var link_pos = node.getConnectionPos(true, i);\n                                if (\n                                    isInsideRectangle(\n                                        e.canvasX,\n                                        e.canvasY,\n                                        link_pos[0] - 15,\n                                        link_pos[1] - 10,\n                                        30,\n                                        20\n                                    )\n                                ) {\n                                    if (is_double_click) {\n                                        if (node.onInputDblClick) {\n                                            node.onInputDblClick(i, e);\n                                        }\n                                    } else {\n                                        if (node.onInputClick) {\n                                            node.onInputClick(i, e);\n                                        }\n                                    }\n\n                                    if (input.link !== null) {\n                                        var link_info = this.graph.links[\n                                            input.link\n                                        ]; //before disconnecting\n                                        if (LiteGraph.click_do_break_link_to){\n                                            node.disconnectInput(i);\n                                            this.dirty_bgcanvas = true;\n                                            skip_action = true;\n                                        }else{\n                                            // do same action as has not node ?\n                                        }\n\n                                        if (\n                                            this.allow_reconnect_links ||\n\t\t\t\t\t\t\t\t\t\t\t//this.move_destination_link_without_shift ||\n                                            e.shiftKey\n                                        ) {\n                                            if (!LiteGraph.click_do_break_link_to){\n                                                node.disconnectInput(i);\n                                            }\n                                            this.connecting_node = this.graph._nodes_by_id[\n                                                link_info.origin_id\n                                            ];\n                                            this.connecting_slot =\n                                                link_info.origin_slot;\n                                            this.connecting_output = this.connecting_node.outputs[\n                                                this.connecting_slot\n                                            ];\n                                            this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot );\n                                            \n                                            this.dirty_bgcanvas = true;\n                                            skip_action = true;\n                                        }\n\n                                        \n                                    }else{\n                                        // has not node\n                                    }\n                                    \n                                    if (!skip_action){\n                                        // connect from in to out, from to to from\n                                        this.connecting_node = node;\n                                        this.connecting_input = input;\n                                        this.connecting_input.slot_index = i;\n                                        this.connecting_pos = node.getConnectionPos( true, i );\n                                        this.connecting_slot = i;\n                                        \n                                        this.dirty_bgcanvas = true;\n                                        skip_action = true;\n                                    }\n                                }\n                            }\n                        }\n                    } //not resizing\n                }\n\n                //it wasn't clicked on the links boxes\n                if (!skip_action) {\n                    var block_drag_node = false;\n\t\t\t\t\tvar pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]];\n\n                    //widgets\n                    var widget = this.processNodeWidgets( node, this.graph_mouse, e );\n                    if (widget) {\n                        block_drag_node = true;\n                        this.node_widget = [node, widget];\n                    }\n\n                    //double clicking\n                    if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) {\n                        //double click node\n                        if (node.onDblClick) {\n                            node.onDblClick( e, pos, this );\n                        }\n                        this.processNodeDblClicked(node);\n                        block_drag_node = true;\n                    }\n\n                    //if do not capture mouse\n                    if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) {\n                        block_drag_node = true;\n                    } else {\n\t\t\t\t\t\t//open subgraph button\n\t\t\t\t\t\tif(node.subgraph && !node.skip_subgraph_button)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) {\n\t\t\t\t\t\t\t\tvar that = this;\n\t\t\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\t\t\tthat.openSubgraph(node.subgraph);\n\t\t\t\t\t\t\t\t}, 10);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (this.live_mode) {\n\t\t\t\t\t\t\tclicking_canvas_bg = true;\n\t                        block_drag_node = true;\n\t\t\t\t\t\t}\n                    }\n\n                    if (!block_drag_node) {\n                        if (this.allow_dragnodes) {\n\t\t\t\t\t\t\tthis.graph.beforeChange();\n                            this.node_dragged = node;\n                        }\n                        this.processNodeSelected(node, e);\n                    } else { // double-click\n                        /**\n                         * Don't call the function if the block is already selected.\n                         * Otherwise, it could cause the block to be unselected while its panel is open.\n                         */\n                        if (!node.is_selected) this.processNodeSelected(node, e);\n                    }\n\n                    this.dirty_canvas = true;\n                }\n            } //clicked outside of nodes\n            else {\n\t\t\t\tif (!skip_action){\n\t\t\t\t\t//search for link connector\n\t\t\t\t\tif(!this.read_only) {\n\t\t\t\t\t\tfor (var i = 0; i < this.visible_links.length; ++i) {\n\t\t\t\t\t\t\tvar link = this.visible_links[i];\n\t\t\t\t\t\t\tvar center = link._pos;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!center ||\n\t\t\t\t\t\t\t\te.canvasX < center[0] - 4 ||\n\t\t\t\t\t\t\t\te.canvasX > center[0] + 4 ||\n\t\t\t\t\t\t\t\te.canvasY < center[1] - 4 ||\n\t\t\t\t\t\t\t\te.canvasY > center[1] + 4\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t//link clicked\n\t\t\t\t\t\t\tthis.showLinkMenu(link, e);\n\t\t\t\t\t\t\tthis.over_link_center = null; //clear tooltip\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY );\n\t\t\t\t\tthis.selected_group_resizing = false;\n\t\t\t\t\tif (this.selected_group && !this.read_only ) {\n\t\t\t\t\t\tif (e.ctrlKey) {\n\t\t\t\t\t\t\tthis.dragging_rectangle = null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] );\n\t\t\t\t\t\tif (dist * this.ds.scale < 10) {\n\t\t\t\t\t\t\tthis.selected_group_resizing = true;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.selected_group.recomputeInsideNodes();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (is_double_click && !this.read_only && this.allow_searchbox) {\n\t\t\t\t\t\tthis.showSearchBox(e);\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t}\n\n\t\t\t\t\tclicking_canvas_bg = true;\n\t\t\t\t}\n            }\n\n            if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) {\n            \t//console.log(\"pointerevents: dragging_canvas start\");\n            \tthis.dragging_canvas = true;\n            }\n            \n        } else if (e.which == 2) {\n            //middle button\n        \t\n\t\t\tif (LiteGraph.middle_click_slot_add_default_node){\n\t\t\t\tif (node && this.allow_interaction && !skip_action && !this.read_only){\n\t\t\t\t\t//not dragging mouse to connect two slots\n\t\t\t\t\tif (\n\t\t\t\t\t\t!this.connecting_node &&\n\t\t\t\t\t\t!node.flags.collapsed &&\n\t\t\t\t\t\t!this.live_mode\n\t\t\t\t\t) {\n\t\t\t\t\t\tvar mClikSlot = false;\n\t\t\t\t\t\tvar mClikSlot_index = false;\n\t\t\t\t\t\tvar mClikSlot_isOut = false;\n\t\t\t\t\t\t//search for outputs\n\t\t\t\t\t\tif (node.outputs) {\n\t\t\t\t\t\t\tfor ( var i = 0, l = node.outputs.length; i < l; ++i ) {\n\t\t\t\t\t\t\t\tvar output = node.outputs[i];\n\t\t\t\t\t\t\t\tvar link_pos = node.getConnectionPos(false, i);\n\t\t\t\t\t\t\t\tif (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) {\n\t\t\t\t\t\t\t\t\tmClikSlot = output;\n\t\t\t\t\t\t\t\t\tmClikSlot_index = i;\n\t\t\t\t\t\t\t\t\tmClikSlot_isOut = true;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t//search for inputs\n\t\t\t\t\t\tif (node.inputs) {\n\t\t\t\t\t\t\tfor ( var i = 0, l = node.inputs.length; i < l; ++i ) {\n\t\t\t\t\t\t\t\tvar input = node.inputs[i];\n\t\t\t\t\t\t\t\tvar link_pos = node.getConnectionPos(true, i);\n\t\t\t\t\t\t\t\tif (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) {\n\t\t\t\t\t\t\t\t\tmClikSlot = input;\n\t\t\t\t\t\t\t\t\tmClikSlot_index = i;\n\t\t\t\t\t\t\t\t\tmClikSlot_isOut = false;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//console.log(\"middleClickSlots? \"+mClikSlot+\" & \"+(mClikSlot_index!==false));\n\t\t\t\t\t\tif (mClikSlot && mClikSlot_index!==false){\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tvar alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length)));\n\t\t\t\t\t\t\tvar node_bounding = node.getBounding();\n\t\t\t\t\t\t\t// estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes\n\t\t\t\t\t\t\tvar posRef = [\t(!mClikSlot_isOut?node_bounding[0]:node_bounding[0]+node_bounding[2])// + node_bounding[0]/this.canvas.width*150\n\t\t\t\t\t\t\t\t\t\t\t,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical \"derive\"\n\t\t\t\t\t\t\t\t\t\t  ];\n\t\t\t\t\t\t\tvar nodeCreated = this.createDefaultNodeForSlot({   \tnodeFrom: !mClikSlot_isOut?null:node\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotFrom: !mClikSlot_isOut?null:mClikSlot_index\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeTo: !mClikSlot_isOut?node:null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotTo: !mClikSlot_isOut?mClikSlot_index:null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,position: posRef //,e: e\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: \"AUTO\" //nodeNewType\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,posAdd:[!mClikSlot_isOut?-30:30, -alphaPosY*130] //-alphaPosY*30]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,posSizeFix:[!mClikSlot_isOut?-1:0, 0] //-alphaPosY*2*/\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (!skip_action && this.allow_dragcanvas) {\n            \t//console.log(\"pointerevents: dragging_canvas start from middle button\");\n            \tthis.dragging_canvas = true;\n            }\n\n        \t\n        } else if (e.which == 3 || this.pointer_is_double) {\n\t\t\t\n            //right button\n\t\t\tif (this.allow_interaction && !skip_action && !this.read_only){\n\t\t\t\t\n\t\t\t\t// is it hover a node ?\n\t\t\t\tif (node){\n\t\t\t\t\tif(Object.keys(this.selected_nodes).length\n\t\t\t\t\t   && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey)\n\t\t\t\t\t){\n\t\t\t\t\t\t// is multiselected or using shift to include the now node\n\t\t\t\t\t\tif (!this.selected_nodes[node.id]) this.selectNodes([node],true); // add this if not present\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// update selection\n\t\t\t\t\t\tthis.selectNodes([node]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// show menu on this node\n\t\t\t\tthis.processContextMenu(node, e);\n\t\t\t}\n\t\t\t\n        }\n\n        //TODO\n        //if(this.node_selected != prev_selected)\n        //\tthis.onNodeSelectionChange(this.node_selected);\n\n        this.last_mouse[0] = e.clientX;\n        this.last_mouse[1] = e.clientY;\n        this.last_mouseclick = LiteGraph.getTime();\n        this.last_mouse_dragging = true;\n\n        /*\n\tif( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)\n\t\tthis.draw();\n\t*/\n\n        this.graph.change();\n\n        //this is to ensure to defocus(blur) if a text input element is on focus\n        if (\n            !ref_window.document.activeElement ||\n            (ref_window.document.activeElement.nodeName.toLowerCase() !=\n                \"input\" &&\n                ref_window.document.activeElement.nodeName.toLowerCase() !=\n                    \"textarea\")\n        ) {\n            e.preventDefault();\n        }\n        e.stopPropagation();\n\n        if (this.onMouseDown) {\n            this.onMouseDown(e);\n        }\n\n        return false;\n    };\n\n    /**\n     * Called when a mouse move event has to be processed\n     * @method processMouseMove\n     **/\n    LGraphCanvas.prototype.processMouseMove = function(e) {\n        if (this.autoresize) {\n            this.resize();\n        }\n\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\n        if (!this.graph) {\n            return;\n        }\n\n        LGraphCanvas.active_canvas = this;\n        this.adjustMouseEvent(e);\n        var mouse = [e.clientX, e.clientY];\n\t\tthis.mouse[0] = mouse[0];\n\t\tthis.mouse[1] = mouse[1];\n        var delta = [\n            mouse[0] - this.last_mouse[0],\n            mouse[1] - this.last_mouse[1]\n        ];\n        this.last_mouse = mouse;\n        this.graph_mouse[0] = e.canvasX;\n        this.graph_mouse[1] = e.canvasY;\n\n        //console.log(\"pointerevents: processMouseMove \"+e.pointerId+\" \"+e.isPrimary);\n        \n\t\tif(this.block_click)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseMove block_click\");\n\t\t\te.preventDefault();\n\t\t\treturn false;\n\t\t}\n\n        e.dragging = this.last_mouse_dragging;\n\n        if (this.node_widget) {\n            this.processNodeWidgets(\n                this.node_widget[0],\n                this.graph_mouse,\n                e,\n                this.node_widget[1]\n            );\n            this.dirty_canvas = true;\n        }\n\n        //get node over\n        var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes);\n\n        if (this.dragging_rectangle)\n\t\t{\n            this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0];\n            this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1];\n            this.dirty_canvas = true;\n        } \n\t\telse if (this.selected_group && !this.read_only)\n\t\t{\n            //moving/resizing a group\n            if (this.selected_group_resizing) {\n                this.selected_group.size = [\n                    e.canvasX - this.selected_group.pos[0],\n                    e.canvasY - this.selected_group.pos[1]\n                ];\n            } else {\n                var deltax = delta[0] / this.ds.scale;\n                var deltay = delta[1] / this.ds.scale;\n                this.selected_group.move(deltax, deltay, e.ctrlKey);\n                if (this.selected_group._nodes.length) {\n                    this.dirty_canvas = true;\n                }\n            }\n            this.dirty_bgcanvas = true;\n        } else if (this.dragging_canvas) {\n        \t////console.log(\"pointerevents: processMouseMove is dragging_canvas\");\n            this.ds.offset[0] += delta[0] / this.ds.scale;\n            this.ds.offset[1] += delta[1] / this.ds.scale;\n            this.dirty_canvas = true;\n            this.dirty_bgcanvas = true;\n        } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) {\n            if (this.connecting_node) {\n                this.dirty_canvas = true;\n            }\n\n            //remove mouseover flag\n            for (var i = 0, l = this.graph._nodes.length; i < l; ++i) {\n                if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) {\n                    //mouse leave\n                    this.graph._nodes[i].mouseOver = false;\n                    if (this.node_over && this.node_over.onMouseLeave) {\n                        this.node_over.onMouseLeave(e);\n                    }\n                    this.node_over = null;\n                    this.dirty_canvas = true;\n                }\n            }\n\n            //mouse over a node\n            if (node) {\n\n\t\t\t\tif(node.redraw_on_mouse)\n                    this.dirty_canvas = true;\n\n                //this.canvas.style.cursor = \"move\";\n                if (!node.mouseOver) {\n                    //mouse enter\n                    node.mouseOver = true;\n                    this.node_over = node;\n                    this.dirty_canvas = true;\n\n                    if (node.onMouseEnter) {\n                        node.onMouseEnter(e);\n                    }\n                }\n\n                //in case the node wants to do something\n                if (node.onMouseMove) {\n                    node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this );\n                }\n\n                //if dragging a link\n                if (this.connecting_node) {\n                    \n                    if (this.connecting_output){\n                        \n                        var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput\n\n                        //on top of input\n                        if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {\n                            //mouse on top of the corner box, don't know what to do\n                        } else {\n                            //check if I have a slot below de mouse\n                            var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos );\n                            if (slot != -1 && node.inputs[slot]) {\n                                var slot_type = node.inputs[slot].type;\n                                if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) {\n                                    this._highlight_input = pos;\n\t\t\t\t\t\t\t\t\tthis._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS\n                                }\n                            } else {\n                                this._highlight_input = null;\n\t\t\t\t\t\t\t\tthis._highlight_input_slot = null;  // XXX CHECK THIS\n                            }\n                        }\n                        \n                    }else if(this.connecting_input){\n                        \n                        var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput\n\n                        //on top of output\n                        if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {\n                            //mouse on top of the corner box, don't know what to do\n                        } else {\n                            //check if I have a slot below de mouse\n                            var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos );\n                            if (slot != -1 && node.outputs[slot]) {\n                                var slot_type = node.outputs[slot].type;\n                                if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) {\n                                    this._highlight_output = pos;\n                                }\n                            } else {\n                                this._highlight_output = null;\n                            }\n                        }\n                    }\n                }\n\n                //Search for corner\n                if (this.canvas) {\n                    if (\n                        isInsideRectangle(\n                            e.canvasX,\n                            e.canvasY,\n                            node.pos[0] + node.size[0] - 5,\n                            node.pos[1] + node.size[1] - 5,\n                            5,\n                            5\n                        )\n                    ) {\n                        this.canvas.style.cursor = \"se-resize\";\n                    } else {\n                        this.canvas.style.cursor = \"crosshair\";\n                    }\n                }\n            } else { //not over a node\n\n                //search for link connector\n\t\t\t\tvar over_link = null;\n\t\t\t\tfor (var i = 0; i < this.visible_links.length; ++i) {\n\t\t\t\t\tvar link = this.visible_links[i];\n\t\t\t\t\tvar center = link._pos;\n\t\t\t\t\tif (\n\t\t\t\t\t\t!center ||\n\t\t\t\t\t\te.canvasX < center[0] - 4 ||\n\t\t\t\t\t\te.canvasX > center[0] + 4 ||\n\t\t\t\t\t\te.canvasY < center[1] - 4 ||\n\t\t\t\t\t\te.canvasY > center[1] + 4\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tover_link = link;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif( over_link != this.over_link_center )\n\t\t\t\t{\n\t\t\t\t\tthis.over_link_center = over_link;\n\t                this.dirty_canvas = true;\n\t\t\t\t}\n\n\t\t\t\tif (this.canvas) {\n\t                this.canvas.style.cursor = \"\";\n\t\t\t\t}\n\t\t\t} //end\n\n\t\t\t//send event to node if capturing input (used with widgets that allow drag outside of the area of the node)\n            if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) {\n                this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this);\n            }\n\n\t\t\t//node being dragged\n            if (this.node_dragged && !this.live_mode) {\n\t\t\t\t//console.log(\"draggin!\",this.selected_nodes);\n                for (var i in this.selected_nodes) {\n                    var n = this.selected_nodes[i];\n                    n.pos[0] += delta[0] / this.ds.scale;\n                    n.pos[1] += delta[1] / this.ds.scale;\n                    if (!n.is_selected) this.processNodeSelected(n, e); /*\n                     * Don't call the function if the block is already selected.\n                     * Otherwise, it could cause the block to be unselected while dragging.\n                     */\n                }\n\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n            }\n\n            if (this.resizing_node && !this.live_mode) {\n                //convert mouse to node space\n\t\t\t\tvar desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ];\n\t\t\t\tvar min_size = this.resizing_node.computeSize();\n\t\t\t\tdesired_size[0] = Math.max( min_size[0], desired_size[0] );\n\t\t\t\tdesired_size[1] = Math.max( min_size[1], desired_size[1] );\n\t\t\t\tthis.resizing_node.setSize( desired_size );\n\n                this.canvas.style.cursor = \"se-resize\";\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n            }\n        }\n\n        e.preventDefault();\n        return false;\n    };\n\n    /**\n     * Called when a mouse up event has to be processed\n     * @method processMouseUp\n     **/\n    LGraphCanvas.prototype.processMouseUp = function(e) {\n\n\t\tvar is_primary = ( e.isPrimary === undefined || e.isPrimary );\n\n    \t//early exit for extra pointer\n    \tif(!is_primary){\n    \t\t/*e.stopPropagation();\n        \te.preventDefault();*/\n    \t\t//console.log(\"pointerevents: processMouseUp pointerN_stop \"+e.pointerId+\" \"+e.isPrimary);\n    \t\treturn false;\n    \t}\n    \t\n    \t//console.log(\"pointerevents: processMouseUp \"+e.pointerId+\" \"+e.isPrimary+\" :: \"+e.clientX+\" \"+e.clientY);\n    \t\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\n        if (!this.graph)\n            return;\n\n        var window = this.getCanvasWindow();\n        var document = window.document;\n        LGraphCanvas.active_canvas = this;\n\n        //restore the mousemove event back to the canvas\n\t\tif(!this.options.skip_events)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseUp adjustEventListener\");\n\t\t\tLiteGraph.pointerListenerRemove(document,\"move\", this._mousemove_callback,true);\n\t\t\tLiteGraph.pointerListenerAdd(this.canvas,\"move\", this._mousemove_callback,true);\n\t\t\tLiteGraph.pointerListenerRemove(document,\"up\", this._mouseup_callback,true);\n\t\t}\n\n        this.adjustMouseEvent(e);\n        var now = LiteGraph.getTime();\n        e.click_time = now - this.last_mouseclick;\n        this.last_mouse_dragging = false;\n\t\tthis.last_click_position = null;\n\n\t\tif(this.block_click)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseUp block_clicks\");\n\t\t\tthis.block_click = false; //used to avoid sending twice a click in a immediate button\n\t\t}\n\n\t\t//console.log(\"pointerevents: processMouseUp which: \"+e.which);\n\t\t\n        if (e.which == 1) {\n\n\t\t\tif( this.node_widget )\n\t\t\t{\n\t\t\t\tthis.processNodeWidgets( this.node_widget[0], this.graph_mouse, e );\n\t\t\t}\n\n            //left button\n            this.node_widget = null;\n\n            if (this.selected_group) {\n                var diffx =\n                    this.selected_group.pos[0] -\n                    Math.round(this.selected_group.pos[0]);\n                var diffy =\n                    this.selected_group.pos[1] -\n                    Math.round(this.selected_group.pos[1]);\n                this.selected_group.move(diffx, diffy, e.ctrlKey);\n                this.selected_group.pos[0] = Math.round(\n                    this.selected_group.pos[0]\n                );\n                this.selected_group.pos[1] = Math.round(\n                    this.selected_group.pos[1]\n                );\n                if (this.selected_group._nodes.length) {\n                    this.dirty_canvas = true;\n                }\n                this.selected_group = null;\n            }\n            this.selected_group_resizing = false;\n\n\t\t\tvar node = this.graph.getNodeOnPos(\n\t\t\t\t\t\t\te.canvasX,\n\t\t\t\t\t\t\te.canvasY,\n\t\t\t\t\t\t\tthis.visible_nodes\n\t\t\t\t\t\t);\n\t\t\t\n            if (this.dragging_rectangle) {\n                if (this.graph) {\n                    var nodes = this.graph._nodes;\n                    var node_bounding = new Float32Array(4);\n                    \n                    //compute bounding and flip if left to right\n                    var w = Math.abs(this.dragging_rectangle[2]);\n                    var h = Math.abs(this.dragging_rectangle[3]);\n                    var startx =\n                        this.dragging_rectangle[2] < 0\n                            ? this.dragging_rectangle[0] - w\n                            : this.dragging_rectangle[0];\n                    var starty =\n                        this.dragging_rectangle[3] < 0\n                            ? this.dragging_rectangle[1] - h\n                            : this.dragging_rectangle[1];\n                    this.dragging_rectangle[0] = startx;\n                    this.dragging_rectangle[1] = starty;\n                    this.dragging_rectangle[2] = w;\n                    this.dragging_rectangle[3] = h;\n\n\t\t\t\t\t// test dragging rect size, if minimun simulate a click\n\t\t\t\t\tif (!node || (w > 10 && h > 10 )){\n\t\t\t\t\t\t//test against all nodes (not visible because the rectangle maybe start outside\n\t\t\t\t\t\tvar to_select = [];\n\t\t\t\t\t\tfor (var i = 0; i < nodes.length; ++i) {\n\t\t\t\t\t\t\tvar nodeX = nodes[i];\n\t\t\t\t\t\t\tnodeX.getBounding(node_bounding);\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!overlapBounding(\n\t\t\t\t\t\t\t\t\tthis.dragging_rectangle,\n\t\t\t\t\t\t\t\t\tnode_bounding\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t} //out of the visible area\n\t\t\t\t\t\t\tto_select.push(nodeX);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (to_select.length) {\n\t\t\t\t\t\t\tthis.selectNodes(to_select,e.shiftKey); // add to selection with shift\n\t\t\t\t\t\t}\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// will select of update selection\n\t\t\t\t\t\tthis.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey\n\t\t\t\t\t}\n\t\t\t\t\t\n                }\n                this.dragging_rectangle = null;\n            } else if (this.connecting_node) {\n                //dragging a connection\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n\n                var connInOrOut = this.connecting_output || this.connecting_input;\n                var connType = connInOrOut.type;\n                \n                //node below mouse\n                if (node) {\n                    \n                    /* no need to condition on event type.. just another type\n                    if (\n                        connType == LiteGraph.EVENT &&\n                        this.isOverNodeBox(node, e.canvasX, e.canvasY)\n                    ) {\n                        \n                        this.connecting_node.connect(\n                            this.connecting_slot,\n                            node,\n                            LiteGraph.EVENT\n                        );\n                        \n                    } else {*/\n                        \n                        //slot below mouse? connect\n                        \n                        if (this.connecting_output){\n                            \n                            var slot = this.isOverNodeInput(\n                                node,\n                                e.canvasX,\n                                e.canvasY\n                            );\n                            if (slot != -1) {\n                                this.connecting_node.connect(this.connecting_slot, node, slot);\n                            } else {\n                                //not on top of an input\n                                // look for a good slot\n                                this.connecting_node.connectByType(this.connecting_slot,node,connType);\n                            }\n                            \n                        }else if (this.connecting_input){\n                            \n                            var slot = this.isOverNodeOutput(\n                                node,\n                                e.canvasX,\n                                e.canvasY\n                            );\n\n                            if (slot != -1) {\n                                node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like\n                            } else {\n                                //not on top of an input\n                                // look for a good slot\n                                this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType);\n                            }\n                            \n                        }\n                        \n                        \n                    //}\n                    \n                }else{\n                    \n                    // add menu when releasing link in empty space\n                \tif (LiteGraph.release_link_on_empty_shows_menu){\n\t                    if (e.shiftKey && this.allow_searchbox){\n\t                        if(this.connecting_output){\n\t                            this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type});\n\t                        }else if(this.connecting_input){\n\t                            this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type});\n\t                        }\n\t                    }else{\n\t                        if(this.connecting_output){\n\t                            this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e});\n\t                        }else if(this.connecting_input){\n\t                            this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e});\n\t                        }\n\t                    }\n                \t}\n                }\n\n                this.connecting_output = null;\n                this.connecting_input = null;\n                this.connecting_pos = null;\n                this.connecting_node = null;\n                this.connecting_slot = -1;\n            } //not dragging connection\n            else if (this.resizing_node) {\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n\t\t\t\tthis.graph.afterChange(this.resizing_node);\n                this.resizing_node = null;\n            } else if (this.node_dragged) {\n                //node being dragged?\n                var node = this.node_dragged;\n                if (\n                    node &&\n                    e.click_time < 300 &&\n                    isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT )\n                ) {\n                    node.collapse();\n                }\n\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n                this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]);\n                this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]);\n                if (this.graph.config.align_to_grid || this.align_to_grid ) {\n                    this.node_dragged.alignToGrid();\n                }\n\t\t\t\tif( this.onNodeMoved )\n\t\t\t\t\tthis.onNodeMoved( this.node_dragged );\n\t\t\t\tthis.graph.afterChange(this.node_dragged);\n                this.node_dragged = null;\n            } //no node being dragged\n            else {\n                //get node over\n                var node = this.graph.getNodeOnPos(\n                    e.canvasX,\n                    e.canvasY,\n                    this.visible_nodes\n                );\n\n                if (!node && e.click_time < 300) {\n                    this.deselectAllNodes();\n                }\n\n                this.dirty_canvas = true;\n                this.dragging_canvas = false;\n\n                if (this.node_over && this.node_over.onMouseUp) {\n                    this.node_over.onMouseUp( e, [ e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1] ], this );\n                }\n                if (\n                    this.node_capturing_input &&\n                    this.node_capturing_input.onMouseUp\n                ) {\n                    this.node_capturing_input.onMouseUp(e, [\n                        e.canvasX - this.node_capturing_input.pos[0],\n                        e.canvasY - this.node_capturing_input.pos[1]\n                    ]);\n                }\n            }\n        } else if (e.which == 2) {\n            //middle button\n            //trace(\"middle\");\n            this.dirty_canvas = true;\n            this.dragging_canvas = false;\n        } else if (e.which == 3) {\n            //right button\n            //trace(\"right\");\n            this.dirty_canvas = true;\n            this.dragging_canvas = false;\n        }\n\n        /*\n\t\tif((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)\n\t\t\tthis.draw();\n\t\t*/\n\n\t  \tif (is_primary)\n\t\t{\n\t\t\tthis.pointer_is_down = false;\n\t\t\tthis.pointer_is_double = false;\n\t\t}\n\t  \n        this.graph.change();\n\n        //console.log(\"pointerevents: processMouseUp stopPropagation\");\n        e.stopPropagation();\n        e.preventDefault();\n        return false;\n    };\n\n    /**\n     * Called when a mouse wheel event has to be processed\n     * @method processMouseWheel\n     **/\n    LGraphCanvas.prototype.processMouseWheel = function(e) {\n        if (!this.graph || !this.allow_dragcanvas) {\n            return;\n        }\n\n        var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60;\n\n        this.adjustMouseEvent(e);\n\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\t\tif(!is_inside)\n\t\t\treturn;\n\n        var scale = this.ds.scale;\n\n        if (delta > 0) {\n            scale *= 1.1;\n        } else if (delta < 0) {\n            scale *= 1 / 1.1;\n        }\n\n        //this.setZoom( scale, [ e.clientX, e.clientY ] );\n        this.ds.changeScale(scale, [e.clientX, e.clientY]);\n\n        this.graph.change();\n\n        e.preventDefault();\n        return false; // prevent default\n    };\n\n    /**\n     * returns true if a position (in graph space) is on top of a node little corner box\n     * @method isOverNodeBox\n     **/\n    LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) {\n        var title_height = LiteGraph.NODE_TITLE_HEIGHT;\n        if (\n            isInsideRectangle(\n                canvasx,\n                canvasy,\n                node.pos[0] + 2,\n                node.pos[1] + 2 - title_height,\n                title_height - 4,\n                title_height - 4\n            )\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    /**\n     * returns the INDEX if a position (in graph space) is on top of a node input slot\n     * @method isOverNodeInput\n     **/\n    LGraphCanvas.prototype.isOverNodeInput = function(\n        node,\n        canvasx,\n        canvasy,\n        slot_pos\n    ) {\n        if (node.inputs) {\n            for (var i = 0, l = node.inputs.length; i < l; ++i) {\n                var input = node.inputs[i];\n                var link_pos = node.getConnectionPos(true, i);\n                var is_inside = false;\n                if (node.horizontal) {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 5,\n                        link_pos[1] - 10,\n                        10,\n                        20\n                    );\n                } else {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        40,\n                        10\n                    );\n                }\n                if (is_inside) {\n                    if (slot_pos) {\n                        slot_pos[0] = link_pos[0];\n                        slot_pos[1] = link_pos[1];\n                    }\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n    \n    /**\n     * returns the INDEX if a position (in graph space) is on top of a node output slot\n     * @method isOverNodeOuput\n     **/\n    LGraphCanvas.prototype.isOverNodeOutput = function(\n        node,\n        canvasx,\n        canvasy,\n        slot_pos\n    ) {\n        if (node.outputs) {\n            for (var i = 0, l = node.outputs.length; i < l; ++i) {\n                var output = node.outputs[i];\n                var link_pos = node.getConnectionPos(false, i);\n                var is_inside = false;\n                if (node.horizontal) {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 5,\n                        link_pos[1] - 10,\n                        10,\n                        20\n                    );\n                } else {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        40,\n                        10\n                    );\n                }\n                if (is_inside) {\n                    if (slot_pos) {\n                        slot_pos[0] = link_pos[0];\n                        slot_pos[1] = link_pos[1];\n                    }\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * process a key event\n     * @method processKey\n     **/\n    LGraphCanvas.prototype.processKey = function(e) {\n        if (!this.graph) {\n            return;\n        }\n\n        var block_default = false;\n        //console.log(e); //debug\n\n        if (e.target.localName == \"input\") {\n            return;\n        }\n\n        if (e.type == \"keydown\") {\n            if (e.keyCode == 32) {\n                //space\n                this.dragging_canvas = true;\n                block_default = true;\n            }\n            \n            if (e.keyCode == 27) {\n                //esc\n                if(this.node_panel) this.node_panel.close();\n                if(this.options_panel) this.options_panel.close();\n                block_default = true;\n            }\n\n            //select all Control A\n            if (e.keyCode == 65 && e.ctrlKey) {\n                this.selectNodes();\n                block_default = true;\n            }\n\n            if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) {\n                //copy\n                if (this.selected_nodes) {\n                    this.copyToClipboard();\n                    block_default = true;\n                }\n            }\n\n            if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) {\n                //paste\n                this.pasteFromClipboard(e.shiftKey);\n            }\n\n            //delete or backspace\n            if (e.keyCode == 46 || e.keyCode == 8) {\n                if (\n                    e.target.localName != \"input\" &&\n                    e.target.localName != \"textarea\"\n                ) {\n                    this.deleteSelectedNodes();\n                    block_default = true;\n                }\n            }\n\n            //collapse\n            //...\n\n            //TODO\n            if (this.selected_nodes) {\n                for (var i in this.selected_nodes) {\n                    if (this.selected_nodes[i].onKeyDown) {\n                        this.selected_nodes[i].onKeyDown(e);\n                    }\n                }\n            }\n        } else if (e.type == \"keyup\") {\n            if (e.keyCode == 32) {\n                // space\n                this.dragging_canvas = false;\n            }\n\n            if (this.selected_nodes) {\n                for (var i in this.selected_nodes) {\n                    if (this.selected_nodes[i].onKeyUp) {\n                        this.selected_nodes[i].onKeyUp(e);\n                    }\n                }\n            }\n        }\n\n        this.graph.change();\n\n        if (block_default) {\n            e.preventDefault();\n            e.stopImmediatePropagation();\n            return false;\n        }\n    };\n\n    LGraphCanvas.prototype.copyToClipboard = function() {\n        var clipboard_info = {\n            nodes: [],\n            links: []\n        };\n        var index = 0;\n        var selected_nodes_array = [];\n        for (var i in this.selected_nodes) {\n            var node = this.selected_nodes[i];\n            if (node.clonable === false)\n                continue;\n            node._relative_id = index;\n            selected_nodes_array.push(node);\n            index += 1;\n        }\n\n        for (var i = 0; i < selected_nodes_array.length; ++i) {\n            var node = selected_nodes_array[i];\n            if(node.clonable === false)\n                continue;\n            var cloned = node.clone();\n            if(!cloned)\n            {\n                console.warn(\"node type not found: \" + node.type );\n                continue;\n            }\n            clipboard_info.nodes.push(cloned.serialize());\n            if (node.inputs && node.inputs.length) {\n                for (var j = 0; j < node.inputs.length; ++j) {\n                    var input = node.inputs[j];\n                    if (!input || input.link == null) {\n                        continue;\n                    }\n                    var link_info = this.graph.links[input.link];\n                    if (!link_info) {\n                        continue;\n                    }\n                    var target_node = this.graph.getNodeById(\n                        link_info.origin_id\n                    );\n                    if (!target_node) {\n                        continue;\n                    }\n                    clipboard_info.links.push([\n                        target_node._relative_id,\n                        link_info.origin_slot, //j,\n                        node._relative_id,\n                        link_info.target_slot,\n                        target_node.id\n                    ]);\n                }\n            }\n        }\n        localStorage.setItem(\n            \"litegrapheditor_clipboard\",\n            JSON.stringify(clipboard_info)\n        );\n    };\n\n    LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) {\n        // if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior\n        if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {\n            return;\n        }\n        var data = localStorage.getItem(\"litegrapheditor_clipboard\");\n        if (!data) {\n            return;\n        }\n\n\t\tthis.graph.beforeChange();\n\n        //create nodes\n        var clipboard_info = JSON.parse(data);\n        // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos\n        var posMin = false;\n        var posMinIndexes = false;\n        for (var i = 0; i < clipboard_info.nodes.length; ++i) {\n            if (posMin){\n                if(posMin[0]>clipboard_info.nodes[i].pos[0]){\n                    posMin[0] = clipboard_info.nodes[i].pos[0];\n                    posMinIndexes[0] = i;\n                }\n                if(posMin[1]>clipboard_info.nodes[i].pos[1]){\n                    posMin[1] = clipboard_info.nodes[i].pos[1];\n                    posMinIndexes[1] = i;\n                }\n            }\n            else{\n                posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]];\n                posMinIndexes = [i, i];\n            }\n        }\n        var nodes = [];\n        for (var i = 0; i < clipboard_info.nodes.length; ++i) {\n            var node_data = clipboard_info.nodes[i];\n            var node = LiteGraph.createNode(node_data.type);\n            if (node) {\n                node.configure(node_data);\n        \n\t\t\t\t//paste in last known mouse position\n                node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5;\n                node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5;\n\n                this.graph.add(node,{doProcessChange:false});\n                \n                nodes.push(node);\n            }\n        }\n\n        //create links\n        for (var i = 0; i < clipboard_info.links.length; ++i) {\n            var link_info = clipboard_info.links[i];\n            var origin_node;\n            var origin_node_relative_id = link_info[0];\n            if (origin_node_relative_id != null) {\n                origin_node = nodes[origin_node_relative_id];\n            } else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {\n                var origin_node_id = link_info[4];\n                if (origin_node_id) {\n                    origin_node = this.graph.getNodeById(origin_node_id);\n                }\n            }\n            var target_node = nodes[link_info[2]];\n\t\t\tif( origin_node && target_node )\n\t            origin_node.connect(link_info[1], target_node, link_info[3]);\n\t\t\telse\n\t\t\t\tconsole.warn(\"Warning, nodes missing on pasting\");\n        }\n\n        this.selectNodes(nodes);\n\n\t\tthis.graph.afterChange();\n    };\n\n    /**\n     * process a item drop event on top the canvas\n     * @method processDrop\n     **/\n    LGraphCanvas.prototype.processDrop = function(e) {\n        e.preventDefault();\n        this.adjustMouseEvent(e);\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\t\tif(!is_inside){\n\t\t\treturn;\n\t\t\t// --- BREAK ---\n\t\t}\n\n        var pos = [e.canvasX, e.canvasY];\n\n\n        var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null;\n\n        if (!node) {\n            var r = null;\n            if (this.onDropItem) {\n                r = this.onDropItem(event);\n            }\n            if (!r) {\n                this.checkDropItem(e);\n            }\n            return;\n        }\n\n        if (node.onDropFile || node.onDropData) {\n            var files = e.dataTransfer.files;\n            if (files && files.length) {\n                for (var i = 0; i < files.length; i++) {\n                    var file = e.dataTransfer.files[0];\n                    var filename = file.name;\n                    var ext = LGraphCanvas.getFileExtension(filename);\n                    //console.log(file);\n\n                    if (node.onDropFile) {\n                        node.onDropFile(file);\n                    }\n\n                    if (node.onDropData) {\n                        //prepare reader\n                        var reader = new FileReader();\n                        reader.onload = function(event) {\n                            //console.log(event.target);\n                            var data = event.target.result;\n                            node.onDropData(data, filename, file);\n                        };\n\n                        //read data\n                        var type = file.type.split(\"/\")[0];\n                        if (type == \"text\" || type == \"\") {\n                            reader.readAsText(file);\n                        } else if (type == \"image\") {\n                            reader.readAsDataURL(file);\n                        } else {\n                            reader.readAsArrayBuffer(file);\n                        }\n                    }\n                }\n            }\n        }\n\n        if (node.onDropItem) {\n            if (node.onDropItem(event)) {\n                return true;\n            }\n        }\n\n        if (this.onDropItem) {\n            return this.onDropItem(event);\n        }\n\n        return false;\n    };\n\n    //called if the graph doesn't have a default drop item behaviour\n    LGraphCanvas.prototype.checkDropItem = function(e) {\n        if (e.dataTransfer.files.length) {\n            var file = e.dataTransfer.files[0];\n            var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase();\n            var nodetype = LiteGraph.node_types_by_file_extension[ext];\n            if (nodetype) {\n\t\t\t\tthis.graph.beforeChange();\n                var node = LiteGraph.createNode(nodetype.type);\n                node.pos = [e.canvasX, e.canvasY];\n                this.graph.add(node);\n                if (node.onDropFile) {\n                    node.onDropFile(file);\n                }\n\t\t\t\tthis.graph.afterChange();\n            }\n        }\n    };\n\n    LGraphCanvas.prototype.processNodeDblClicked = function(n) {\n        if (this.onShowNodePanel) {\n            this.onShowNodePanel(n);\n        }\n\t\telse\n\t\t{\n\t\t\tthis.showShowNodePanel(n);\n\t\t}\n\n        if (this.onNodeDblClicked) {\n            this.onNodeDblClicked(n);\n        }\n\n        this.setDirty(true);\n    };\n\n    LGraphCanvas.prototype.processNodeSelected = function(node, e) {\n        this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select));\n        if (this.onNodeSelected) {\n            this.onNodeSelected(node);\n        }\n    };\n\n    /**\n     * selects a given node (or adds it to the current selection)\n     * @method selectNode\n     **/\n    LGraphCanvas.prototype.selectNode = function(\n        node,\n        add_to_current_selection\n    ) {\n        if (node == null) {\n            this.deselectAllNodes();\n        } else {\n            this.selectNodes([node], add_to_current_selection);\n        }\n    };\n\n    /**\n     * selects several nodes (or adds them to the current selection)\n     * @method selectNodes\n     **/\n    LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection )\n\t{\n\t\tif (!add_to_current_selection) {\n            this.deselectAllNodes();\n        }\n\n        nodes = nodes || this.graph._nodes;\n\t\tif (typeof nodes == \"string\") nodes = [nodes];\n        for (var i in nodes) {\n            var node = nodes[i];\n            if (node.is_selected) {\n                this.deselectNode(node);\n                continue;\n            }\n\n            if (!node.is_selected && node.onSelected) {\n                node.onSelected();\n            }\n            node.is_selected = true;\n            this.selected_nodes[node.id] = node;\n\n            if (node.inputs) {\n                for (var j = 0; j < node.inputs.length; ++j) {\n                    this.highlighted_links[node.inputs[j].link] = true;\n                }\n            }\n            if (node.outputs) {\n                for (var j = 0; j < node.outputs.length; ++j) {\n                    var out = node.outputs[j];\n                    if (out.links) {\n                        for (var k = 0; k < out.links.length; ++k) {\n                            this.highlighted_links[out.links[k]] = true;\n                        }\n                    }\n                }\n            }\n        }\n\n\t\tif(\tthis.onSelectionChange )\n\t\t\tthis.onSelectionChange( this.selected_nodes );\n\n        this.setDirty(true);\n    };\n\n    /**\n     * removes a node from the current selection\n     * @method deselectNode\n     **/\n    LGraphCanvas.prototype.deselectNode = function(node) {\n        if (!node.is_selected) {\n            return;\n        }\n        if (node.onDeselected) {\n            node.onDeselected();\n        }\n        node.is_selected = false;\n\n        if (this.onNodeDeselected) {\n            this.onNodeDeselected(node);\n        }\n\n        //remove highlighted\n        if (node.inputs) {\n            for (var i = 0; i < node.inputs.length; ++i) {\n                delete this.highlighted_links[node.inputs[i].link];\n            }\n        }\n        if (node.outputs) {\n            for (var i = 0; i < node.outputs.length; ++i) {\n                var out = node.outputs[i];\n                if (out.links) {\n                    for (var j = 0; j < out.links.length; ++j) {\n                        delete this.highlighted_links[out.links[j]];\n                    }\n                }\n            }\n        }\n    };\n\n    /**\n     * removes all nodes from the current selection\n     * @method deselectAllNodes\n     **/\n    LGraphCanvas.prototype.deselectAllNodes = function() {\n        if (!this.graph) {\n            return;\n        }\n        var nodes = this.graph._nodes;\n        for (var i = 0, l = nodes.length; i < l; ++i) {\n            var node = nodes[i];\n            if (!node.is_selected) {\n                continue;\n            }\n            if (node.onDeselected) {\n                node.onDeselected();\n            }\n            node.is_selected = false;\n\t\t\tif (this.onNodeDeselected) {\n\t\t\t\tthis.onNodeDeselected(node);\n\t\t\t}\n        }\n        this.selected_nodes = {};\n        this.current_node = null;\n        this.highlighted_links = {};\n\t\tif(\tthis.onSelectionChange )\n\t\t\tthis.onSelectionChange( this.selected_nodes );\n        this.setDirty(true);\n    };\n\n    /**\n     * deletes all nodes in the current selection from the graph\n     * @method deleteSelectedNodes\n     **/\n    LGraphCanvas.prototype.deleteSelectedNodes = function() {\n\n\t\tthis.graph.beforeChange();\n\n        for (var i in this.selected_nodes) {\n            var node = this.selected_nodes[i];\n\n\t\t\tif(node.block_delete)\n\t\t\t\tcontinue;\n\n\t\t\t//autoconnect when possible (very basic, only takes into account first input-output)\n\t\t\tif(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) \n\t\t\t{\n\t\t\t\tvar input_link = node.graph.links[ node.inputs[0].link ];\n\t\t\t\tvar output_link = node.graph.links[ node.outputs[0].links[0] ];\n\t\t\t\tvar input_node = node.getInputNode(0);\n\t\t\t\tvar output_node = node.getOutputNodes(0)[0];\n\t\t\t\tif(input_node && output_node)\n\t\t\t\t\tinput_node.connect( input_link.origin_slot, output_node, output_link.target_slot );\n\t\t\t}\n            this.graph.remove(node);\n\t\t\tif (this.onNodeDeselected) {\n\t\t\t\tthis.onNodeDeselected(node);\n\t\t\t}\n        }\n        this.selected_nodes = {};\n        this.current_node = null;\n        this.highlighted_links = {};\n        this.setDirty(true);\n\t\tthis.graph.afterChange();\n    };\n    \n    /**\n     * centers the camera on a given node\n     * @method centerOnNode\n     **/\n    LGraphCanvas.prototype.centerOnNode = function(node) {\n        this.ds.offset[0] =\n            -node.pos[0] -\n            node.size[0] * 0.5 +\n            (this.canvas.width * 0.5) / this.ds.scale;\n        this.ds.offset[1] =\n            -node.pos[1] -\n            node.size[1] * 0.5 +\n            (this.canvas.height * 0.5) / this.ds.scale;\n        this.setDirty(true, true);\n    };\n\n    /**\n     * adds some useful properties to a mouse event, like the position in graph coordinates\n     * @method adjustMouseEvent\n     **/\n    LGraphCanvas.prototype.adjustMouseEvent = function(e) {\n\tvar clientX_rel = 0;\n        var clientY_rel = 0;\n\t    \n    \tif (this.canvas) {\n            var b = this.canvas.getBoundingClientRect();\n            clientX_rel = e.clientX - b.left;\n            clientY_rel = e.clientY - b.top;\n        } else {\n        \tclientX_rel = e.clientX;\n        \tclientY_rel = e.clientY;\n        }\n    \t\n        // e.deltaX = clientX_rel - this.last_mouse_position[0];\n        // e.deltaY = clientY_rel- this.last_mouse_position[1];\n\n        this.last_mouse_position[0] = clientX_rel;\n        this.last_mouse_position[1] = clientY_rel;\n\n        e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0];\n        e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1];\n        \n        //console.log(\"pointerevents: adjustMouseEvent \"+e.clientX+\":\"+e.clientY+\" \"+clientX_rel+\":\"+clientY_rel+\" \"+e.canvasX+\":\"+e.canvasY);\n    };\n\n    /**\n     * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom\n     * @method setZoom\n     **/\n    LGraphCanvas.prototype.setZoom = function(value, zooming_center) {\n        this.ds.changeScale(value, zooming_center);\n        /*\n\tif(!zooming_center && this.canvas)\n\t\tzooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5];\n\n\tvar center = this.convertOffsetToCanvas( zooming_center );\n\n\tthis.ds.scale = value;\n\n\tif(this.scale > this.max_zoom)\n\t\tthis.scale = this.max_zoom;\n\telse if(this.scale < this.min_zoom)\n\t\tthis.scale = this.min_zoom;\n\n\tvar new_center = this.convertOffsetToCanvas( zooming_center );\n\tvar delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];\n\n\tthis.offset[0] += delta_offset[0];\n\tthis.offset[1] += delta_offset[1];\n\t*/\n\n        this.dirty_canvas = true;\n        this.dirty_bgcanvas = true;\n    };\n\n    /**\n     * converts a coordinate from graph coordinates to canvas2D coordinates\n     * @method convertOffsetToCanvas\n     **/\n    LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) {\n        return this.ds.convertOffsetToCanvas(pos, out);\n    };\n\n    /**\n     * converts a coordinate from Canvas2D coordinates to graph space\n     * @method convertCanvasToOffset\n     **/\n    LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) {\n        return this.ds.convertCanvasToOffset(pos, out);\n    };\n\n    //converts event coordinates from canvas2D to graph coordinates\n    LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) {\n        var rect = this.canvas.getBoundingClientRect();\n        return this.convertCanvasToOffset([\n            e.clientX - rect.left,\n            e.clientY - rect.top\n        ]);\n    };\n\n    /**\n     * brings a node to front (above all other nodes)\n     * @method bringToFront\n     **/\n    LGraphCanvas.prototype.bringToFront = function(node) {\n        var i = this.graph._nodes.indexOf(node);\n        if (i == -1) {\n            return;\n        }\n\n        this.graph._nodes.splice(i, 1);\n        this.graph._nodes.push(node);\n    };\n\n    /**\n     * sends a node to the back (below all other nodes)\n     * @method sendToBack\n     **/\n    LGraphCanvas.prototype.sendToBack = function(node) {\n        var i = this.graph._nodes.indexOf(node);\n        if (i == -1) {\n            return;\n        }\n\n        this.graph._nodes.splice(i, 1);\n        this.graph._nodes.unshift(node);\n    };\n\n    /* Interaction */\n\n    /* LGraphCanvas render */\n    var temp = new Float32Array(4);\n\n    /**\n     * checks which nodes are visible (inside the camera area)\n     * @method computeVisibleNodes\n     **/\n    LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) {\n        var visible_nodes = out || [];\n        visible_nodes.length = 0;\n        nodes = nodes || this.graph._nodes;\n        for (var i = 0, l = nodes.length; i < l; ++i) {\n            var n = nodes[i];\n\n            //skip rendering nodes in live mode\n            if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) {\n                continue;\n            }\n\n            if (!overlapBounding(this.visible_area, n.getBounding(temp, true))) {\n                continue;\n            } //out of the visible area\n\n            visible_nodes.push(n);\n        }\n        return visible_nodes;\n    };\n\n    /**\n     * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)\n     * @method draw\n     **/\n    LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) {\n        if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) {\n            return;\n        }\n\n        //fps counting\n        var now = LiteGraph.getTime();\n        this.render_time = (now - this.last_draw_time) * 0.001;\n        this.last_draw_time = now;\n\n        if (this.graph) {\n            this.ds.computeVisibleArea(this.viewport);\n        }\n\n        if (\n            this.dirty_bgcanvas ||\n            force_bgcanvas ||\n            this.always_render_background ||\n            (this.graph &&\n                this.graph._last_trigger_time &&\n                now - this.graph._last_trigger_time < 1000)\n        ) {\n            this.drawBackCanvas();\n        }\n\n        if (this.dirty_canvas || force_canvas) {\n            this.drawFrontCanvas();\n        }\n\n        this.fps = this.render_time ? 1.0 / this.render_time : 0;\n        this.frame += 1;\n    };\n\n    /**\n     * draws the front canvas (the one containing all the nodes)\n     * @method drawFrontCanvas\n     **/\n    LGraphCanvas.prototype.drawFrontCanvas = function() {\n        this.dirty_canvas = false;\n\n        if (!this.ctx) {\n            this.ctx = this.bgcanvas.getContext(\"2d\");\n        }\n        var ctx = this.ctx;\n        if (!ctx) {\n            //maybe is using webgl...\n            return;\n        }\n\n        var canvas = this.canvas;\n        if ( ctx.start2D && !this.viewport ) {\n            ctx.start2D();\n\t\t\tctx.restore();\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n        }\n\n        //clip dirty area if there is one, otherwise work in full canvas\n\t\tvar area = this.viewport || this.dirty_area;\n        if (area) {\n            ctx.save();\n            ctx.beginPath();\n            ctx.rect( area[0],area[1],area[2],area[3] );\n            ctx.clip();\n        }\n\n        //clear\n        //canvas.width = canvas.width;\n        if (this.clear_background) {\n\t\t\tif(area)\n\t            ctx.clearRect( area[0],area[1],area[2],area[3] );\n\t\t\telse\n\t            ctx.clearRect(0, 0, canvas.width, canvas.height);\n        }\n\n        //draw bg canvas\n        if (this.bgcanvas == this.canvas) {\n            this.drawBackCanvas();\n        } else {\n            ctx.drawImage( this.bgcanvas, 0, 0 );\n        }\n\n        //rendering\n        if (this.onRender) {\n            this.onRender(canvas, ctx);\n        }\n\n        //info widget\n        if (this.show_info) {\n            this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0 );\n        }\n\n        if (this.graph) {\n            //apply transformations\n            ctx.save();\n            this.ds.toCanvasContext(ctx);\n\n            //draw nodes\n            var drawn_nodes = 0;\n            var visible_nodes = this.computeVisibleNodes(\n                null,\n                this.visible_nodes\n            );\n\n            for (var i = 0; i < visible_nodes.length; ++i) {\n                var node = visible_nodes[i];\n\n                //transform coords system\n                ctx.save();\n                ctx.translate(node.pos[0], node.pos[1]);\n\n                //Draw\n                this.drawNode(node, ctx);\n                drawn_nodes += 1;\n\n                //Restore\n                ctx.restore();\n            }\n\n            //on top (debug)\n            if (this.render_execution_order) {\n                this.drawExecutionOrder(ctx);\n            }\n\n            //connections ontop?\n            if (this.graph.config.links_ontop) {\n                if (!this.live_mode) {\n                    this.drawConnections(ctx);\n                }\n            }\n\n            //current connection (the one being dragged by the mouse)\n            if (this.connecting_pos != null) {\n                ctx.lineWidth = this.connections_width;\n                var link_color = null;\n                \n                var connInOrOut = this.connecting_output || this.connecting_input;\n\n                var connType = connInOrOut.type;\n                var connDir = connInOrOut.dir;\n\t\t\t\tif(connDir == null)\n\t\t\t\t{\n\t\t\t\t\tif (this.connecting_output)\n\t\t\t\t\t\tconnDir = this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT;\n\t\t\t\t\telse\n\t\t\t\t\t\tconnDir = this.connecting_node.horizontal ? LiteGraph.UP : LiteGraph.LEFT;\n\t\t\t\t}\n                var connShape = connInOrOut.shape;\n                \n                switch (connType) {\n                    case LiteGraph.EVENT:\n                        link_color = LiteGraph.EVENT_LINK_COLOR;\n                        break;\n                    default:\n                        link_color = LiteGraph.CONNECTING_LINK_COLOR;\n                }\n\n                //the connection being dragged by the mouse\n                this.renderLink(\n                    ctx,\n                    this.connecting_pos,\n                    [this.graph_mouse[0], this.graph_mouse[1]],\n                    null,\n                    false,\n                    null,\n                    link_color,\n                    connDir,\n                    LiteGraph.CENTER\n                );\n\n                ctx.beginPath();\n                if (\n                    connType === LiteGraph.EVENT ||\n                    connShape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(\n                        this.connecting_pos[0] - 6 + 0.5,\n                        this.connecting_pos[1] - 5 + 0.5,\n                        14,\n                        10\n                    );\n\t                ctx.fill();\n\t\t\t\t\tctx.beginPath();\n                    ctx.rect(\n                        this.graph_mouse[0] - 6 + 0.5,\n                        this.graph_mouse[1] - 5 + 0.5,\n                        14,\n                        10\n                    );\n                } else if (connShape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5);\n                    ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5);\n                    ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5);\n                    ctx.closePath();\n                } \n                else {\n                    ctx.arc(\n                        this.connecting_pos[0],\n                        this.connecting_pos[1],\n                        4,\n                        0,\n                        Math.PI * 2\n                    );\n\t                ctx.fill();\n\t\t\t\t\tctx.beginPath();\n                    ctx.arc(\n                        this.graph_mouse[0],\n                        this.graph_mouse[1],\n                        4,\n                        0,\n                        Math.PI * 2\n                    );\n                }\n                ctx.fill();\n\n                ctx.fillStyle = \"#ffcc00\";\n                if (this._highlight_input) {\n                    ctx.beginPath();\n                    var shape = this._highlight_input_slot.shape;\n                    if (shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5);\n                        ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5);\n                        ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else {\n                        ctx.arc(\n                            this._highlight_input[0],\n                            this._highlight_input[1],\n                            6,\n                            0,\n                            Math.PI * 2\n                        );\n                    }\n                    ctx.fill();\n                }\n                if (this._highlight_output) {\n                    ctx.beginPath();\n                    if (shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5);\n                        ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5);\n                        ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else {\n                        ctx.arc(\n                            this._highlight_output[0],\n                            this._highlight_output[1],\n                            6,\n                            0,\n                            Math.PI * 2\n                        );\n                    }\n                    ctx.fill();\n                }\n            }\n\n\t\t\t//the selection rectangle\n            if (this.dragging_rectangle) {\n                ctx.strokeStyle = \"#FFF\";\n                ctx.strokeRect(\n                    this.dragging_rectangle[0],\n                    this.dragging_rectangle[1],\n                    this.dragging_rectangle[2],\n                    this.dragging_rectangle[3]\n                );\n            }\n\n\t\t\t//on top of link center\n\t\t\tif(this.over_link_center && this.render_link_tooltip)\n\t\t\t\tthis.drawLinkTooltip( ctx, this.over_link_center );\n\t\t\telse\n\t\t\t\tif(this.onDrawLinkTooltip) //to remove\n\t\t\t\t\tthis.onDrawLinkTooltip(ctx,null);\n\n\t\t\t//custom info\n            if (this.onDrawForeground) {\n                this.onDrawForeground(ctx, this.visible_rect);\n            }\n\n            ctx.restore();\n        }\n\n\t\t//draws panel in the corner \n\t\tif (this._graph_stack && this._graph_stack.length) {\n\t\t\tthis.drawSubgraphPanel( ctx );\n\t\t}\n\n\n        if (this.onDrawOverlay) {\n            this.onDrawOverlay(ctx);\n        }\n\n        if (area){\n            ctx.restore();\n        }\n\n        if (ctx.finish2D) {\n            //this is a function I use in webgl renderer\n            ctx.finish2D();\n        }\n    };\n\n    /**\n     * draws the panel in the corner that shows subgraph properties\n     * @method drawSubgraphPanel\n     **/\n    LGraphCanvas.prototype.drawSubgraphPanel = function (ctx) {\n        var subgraph = this.graph;\n        var subnode = subgraph._subgraph_node;\n        if (!subnode) {\n            console.warn(\"subgraph without subnode\");\n            return;\n        }\n        this.drawSubgraphPanelLeft(subgraph, subnode, ctx)\n        this.drawSubgraphPanelRight(subgraph, subnode, ctx)\n    }\n\n    LGraphCanvas.prototype.drawSubgraphPanelLeft = function (subgraph, subnode, ctx) {\n        var num = subnode.inputs ? subnode.inputs.length : 0;\n        var w = 200;\n        var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6);\n\n        ctx.fillStyle = \"#111\";\n        ctx.globalAlpha = 0.8;\n        ctx.beginPath();\n        ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]);\n        ctx.fill();\n        ctx.globalAlpha = 1;\n\n        ctx.fillStyle = \"#888\";\n        ctx.font = \"14px Arial\";\n        ctx.textAlign = \"left\";\n        ctx.fillText(\"Graph Inputs\", 20, 34);\n        // var pos = this.mouse;\n\n        if (this.drawButton(w - 20, 20, 20, 20, \"X\", \"#151515\")) {\n            this.closeSubgraph();\n            return;\n        }\n\n        var y = 50;\n        ctx.font = \"14px Arial\";\n        if (subnode.inputs)\n            for (var i = 0; i < subnode.inputs.length; ++i) {\n                var input = subnode.inputs[i];\n                if (input.not_subgraph_input)\n                    continue;\n\n                //input button clicked\n                if (this.drawButton(20, y + 2, w - 20, h - 2)) {\n                    var type = subnode.constructor.input_node_type || \"graph/input\";\n                    this.graph.beforeChange();\n                    var newnode = LiteGraph.createNode(type);\n                    if (newnode) {\n                        subgraph.add(newnode);\n                        this.block_click = false;\n                        this.last_click_position = null;\n                        this.selectNodes([newnode]);\n                        this.node_dragged = newnode;\n                        this.dragging_canvas = false;\n                        newnode.setProperty(\"name\", input.name);\n                        newnode.setProperty(\"type\", input.type);\n                        this.node_dragged.pos[0] = this.graph_mouse[0] - 5;\n                        this.node_dragged.pos[1] = this.graph_mouse[1] - 5;\n                        this.graph.afterChange();\n                    }\n                    else\n                        console.error(\"graph input node not found:\", type);\n                }\n                ctx.fillStyle = \"#9C9\";\n                ctx.beginPath();\n                ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI);\n                ctx.fill();\n                ctx.fillStyle = \"#AAA\";\n                ctx.fillText(input.name, 30, y + h * 0.75);\n                // var tw = ctx.measureText(input.name);\n                ctx.fillStyle = \"#777\";\n                ctx.fillText(input.type, 130, y + h * 0.75);\n                y += h;\n            }\n        //add + button\n        if (this.drawButton(20, y + 2, w - 20, h - 2, \"+\", \"#151515\", \"#222\")) {\n            this.showSubgraphPropertiesDialog(subnode);\n        }\n    }\n    LGraphCanvas.prototype.drawSubgraphPanelRight = function (subgraph, subnode, ctx) {\n        var num = subnode.outputs ? subnode.outputs.length : 0;\n        var canvas_w = this.bgcanvas.width\n        var w = 200;\n        var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6);\n\n        ctx.fillStyle = \"#111\";\n        ctx.globalAlpha = 0.8;\n        ctx.beginPath();\n        ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]);\n        ctx.fill();\n        ctx.globalAlpha = 1;\n\n        ctx.fillStyle = \"#888\";\n        ctx.font = \"14px Arial\";\n        ctx.textAlign = \"left\";\n        var title_text = \"Graph Outputs\"\n        var tw = ctx.measureText(title_text).width\n        ctx.fillText(title_text, (canvas_w - tw) - 20, 34);\n        // var pos = this.mouse;\n        if (this.drawButton(canvas_w - w, 20, 20, 20, \"X\", \"#151515\")) {\n            this.closeSubgraph();\n            return;\n        }\n\n        var y = 50;\n        ctx.font = \"14px Arial\";\n        if (subnode.outputs)\n            for (var i = 0; i < subnode.outputs.length; ++i) {\n                var output = subnode.outputs[i];\n                if (output.not_subgraph_input)\n                    continue;\n\n                //output button clicked\n                if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2)) {\n                    var type = subnode.constructor.output_node_type || \"graph/output\";\n                    this.graph.beforeChange();\n                    var newnode = LiteGraph.createNode(type);\n                    if (newnode) {\n                        subgraph.add(newnode);\n                        this.block_click = false;\n                        this.last_click_position = null;\n                        this.selectNodes([newnode]);\n                        this.node_dragged = newnode;\n                        this.dragging_canvas = false;\n                        newnode.setProperty(\"name\", output.name);\n                        newnode.setProperty(\"type\", output.type);\n                        this.node_dragged.pos[0] = this.graph_mouse[0] - 5;\n                        this.node_dragged.pos[1] = this.graph_mouse[1] - 5;\n                        this.graph.afterChange();\n                    }\n                    else\n                        console.error(\"graph input node not found:\", type);\n                }\n                ctx.fillStyle = \"#9C9\";\n                ctx.beginPath();\n                ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI);\n                ctx.fill();\n                ctx.fillStyle = \"#AAA\";\n                ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75);\n                // var tw = ctx.measureText(input.name);\n                ctx.fillStyle = \"#777\";\n                ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75);\n                y += h;\n            }\n        //add + button\n        if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2, \"+\", \"#151515\", \"#222\")) {\n            this.showSubgraphPropertiesDialogRight(subnode);\n        }\n    }\n\t//Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm\n\tLGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor )\n\t{\n\t\tvar ctx = this.ctx;\n\t\tbgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR;\n\t\thovercolor = hovercolor || \"#555\";\n\t\ttextcolor = textcolor || LiteGraph.NODE_TEXT_COLOR;\n\t\tvar pos = this.ds.convertOffsetToCanvas(this.graph_mouse);\n\t\tvar hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tpos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null;\n        if(pos) {\n            var rect = this.canvas.getBoundingClientRect();\n            pos[0] -= rect.left;\n            pos[1] -= rect.top;\n        }\n\t\tvar clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\n\t\tctx.fillStyle = hover ? hovercolor : bgcolor;\n\t\tif(clicked)\n\t\t\tctx.fillStyle = \"#AAA\";\n\t\tctx.beginPath();\n\t\tctx.roundRect(x,y,w,h,[4] );\n\t\tctx.fill();\n\n\t\tif(text != null)\n\t\t{\n\t\t\tif(text.constructor == String)\n\t\t\t{\n\t\t\t\tctx.fillStyle = textcolor;\n\t\t\t\tctx.textAlign = \"center\";\n\t\t\t\tctx.font = ((h * 0.65)|0) + \"px Arial\";\n\t\t\t\tctx.fillText( text, x + w * 0.5,y + h * 0.75 );\n\t\t\t\tctx.textAlign = \"left\";\n\t\t\t}\n\t\t}\n\n\t\tvar was_clicked = clicked && !this.block_click;\n\t\tif(clicked)\n\t\t\tthis.blockClick();\n\t\treturn was_clicked;\n\t}\n\n\tLGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click )\n\t{\n\t\tvar pos = this.mouse;\n\t\tvar hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tpos = this.last_click_position;\n\t\tvar clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tvar was_clicked = clicked && !this.block_click;\n\t\tif(clicked && hold_click)\n\t\t\tthis.blockClick();\n\t\treturn was_clicked;\n\t}\n\n    /**\n     * draws some useful stats in the corner of the canvas\n     * @method renderInfo\n     **/\n    LGraphCanvas.prototype.renderInfo = function(ctx, x, y) {\n        x = x || 10;\n        y = y || this.canvas.height - 80;\n\n        ctx.save();\n        ctx.translate(x, y);\n\n        ctx.font = \"10px Arial\";\n        ctx.fillStyle = \"#888\";\n\t\tctx.textAlign = \"left\";\n        if (this.graph) {\n            ctx.fillText( \"T: \" + this.graph.globaltime.toFixed(2) + \"s\", 5, 13 * 1 );\n            ctx.fillText(\"I: \" + this.graph.iteration, 5, 13 * 2 );\n            ctx.fillText(\"N: \" + this.graph._nodes.length + \" [\" + this.visible_nodes.length + \"]\", 5, 13 * 3 );\n            ctx.fillText(\"V: \" + this.graph._version, 5, 13 * 4);\n            ctx.fillText(\"FPS:\" + this.fps.toFixed(2), 5, 13 * 5);\n        } else {\n            ctx.fillText(\"No graph selected\", 5, 13 * 1);\n        }\n        ctx.restore();\n    };\n\n    /**\n     * draws the back canvas (the one containing the background and the connections)\n     * @method drawBackCanvas\n     **/\n    LGraphCanvas.prototype.drawBackCanvas = function() {\n        var canvas = this.bgcanvas;\n        if (\n            canvas.width != this.canvas.width ||\n            canvas.height != this.canvas.height\n        ) {\n            canvas.width = this.canvas.width;\n            canvas.height = this.canvas.height;\n        }\n\n        if (!this.bgctx) {\n            this.bgctx = this.bgcanvas.getContext(\"2d\");\n        }\n        var ctx = this.bgctx;\n        if (ctx.start) {\n            ctx.start();\n        }\n\n\t\tvar viewport = this.viewport || [0,0,ctx.canvas.width,ctx.canvas.height];\n\n        //clear\n        if (this.clear_background) {\n            ctx.clearRect( viewport[0], viewport[1], viewport[2], viewport[3] );\n        }\n\n\t\t//show subgraph stack header\n        if (this._graph_stack && this._graph_stack.length) {\n            ctx.save();\n            var parent_graph = this._graph_stack[this._graph_stack.length - 1];\n            var subgraph_node = this.graph._subgraph_node;\n            ctx.strokeStyle = subgraph_node.bgcolor;\n            ctx.lineWidth = 10;\n            ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2);\n            ctx.lineWidth = 1;\n            ctx.font = \"40px Arial\";\n            ctx.textAlign = \"center\";\n            ctx.fillStyle = subgraph_node.bgcolor || \"#AAA\";\n            var title = \"\";\n            for (var i = 1; i < this._graph_stack.length; ++i) {\n                title +=\n                    this._graph_stack[i]._subgraph_node.getTitle() + \" >> \";\n            }\n            ctx.fillText(\n                title + subgraph_node.getTitle(),\n                canvas.width * 0.5,\n                40\n            );\n            ctx.restore();\n        }\n\n        var bg_already_painted = false;\n        if (this.onRenderBackground) {\n            bg_already_painted = this.onRenderBackground(canvas, ctx);\n        }\n\n        //reset in case of error\n        if ( !this.viewport )\n\t\t{\n\t        ctx.restore();\n\t\t    ctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t}\n        this.visible_links.length = 0;\n\n        if (this.graph) {\n            //apply transformations\n            ctx.save();\n            this.ds.toCanvasContext(ctx);\n\n            //render BG\n            if ( this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color )\n            {\n                ctx.fillStyle = this.clear_background_color;\n                ctx.fillRect(\n                    this.visible_area[0],\n                    this.visible_area[1],\n                    this.visible_area[2],\n                    this.visible_area[3]\n                );\n            }\n\n            if (\n                this.background_image &&\n                this.ds.scale > 0.5 &&\n                !bg_already_painted\n            ) {\n                if (this.zoom_modify_alpha) {\n                    ctx.globalAlpha =\n                        (1.0 - 0.5 / this.ds.scale) * this.editor_alpha;\n                } else {\n                    ctx.globalAlpha = this.editor_alpha;\n                }\n                ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = \n                if (\n                    !this._bg_img ||\n                    this._bg_img.name != this.background_image\n                ) {\n                    this._bg_img = new Image();\n                    this._bg_img.name = this.background_image;\n                    this._bg_img.src = this.background_image;\n                    var that = this;\n                    this._bg_img.onload = function() {\n                        that.draw(true, true);\n                    };\n                }\n\n                var pattern = null;\n                if (this._pattern == null && this._bg_img.width > 0) {\n                    pattern = ctx.createPattern(this._bg_img, \"repeat\");\n                    this._pattern_img = this._bg_img;\n                    this._pattern = pattern;\n                } else {\n                    pattern = this._pattern;\n                }\n                if (pattern) {\n                    ctx.fillStyle = pattern;\n                    ctx.fillRect(\n                        this.visible_area[0],\n                        this.visible_area[1],\n                        this.visible_area[2],\n                        this.visible_area[3]\n                    );\n                    ctx.fillStyle = \"transparent\";\n                }\n\n                ctx.globalAlpha = 1.0;\n                ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled\n            }\n\n            //groups\n            if (this.graph._groups.length && !this.live_mode) {\n                this.drawGroups(canvas, ctx);\n            }\n\n            if (this.onDrawBackground) {\n                this.onDrawBackground(ctx, this.visible_area);\n            }\n            if (this.onBackgroundRender) {\n                //LEGACY\n                console.error(\n                    \"WARNING! onBackgroundRender deprecated, now is named onDrawBackground \"\n                );\n                this.onBackgroundRender = null;\n            }\n\n            //DEBUG: show clipping area\n            //ctx.fillStyle = \"red\";\n            //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20);\n\n            //bg\n            if (this.render_canvas_border) {\n                ctx.strokeStyle = \"#235\";\n                ctx.strokeRect(0, 0, canvas.width, canvas.height);\n            }\n\n            if (this.render_connections_shadows) {\n                ctx.shadowColor = \"#000\";\n                ctx.shadowOffsetX = 0;\n                ctx.shadowOffsetY = 0;\n                ctx.shadowBlur = 6;\n            } else {\n                ctx.shadowColor = \"rgba(0,0,0,0)\";\n            }\n\n            //draw connections\n            if (!this.live_mode) {\n                this.drawConnections(ctx);\n            }\n\n            ctx.shadowColor = \"rgba(0,0,0,0)\";\n\n            //restore state\n            ctx.restore();\n        }\n\n        if (ctx.finish) {\n            ctx.finish();\n        }\n\n        this.dirty_bgcanvas = false;\n        this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas\n    };\n\n    var temp_vec2 = new Float32Array(2);\n\n    /**\n     * draws the given node inside the canvas\n     * @method drawNode\n     **/\n    LGraphCanvas.prototype.drawNode = function(node, ctx) {\n        var glow = false;\n        this.current_node = node;\n\n        var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR;\n        var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;\n\n        //shadow and glow\n        if (node.mouseOver) {\n            glow = true;\n        }\n\n        var low_quality = this.ds.scale < 0.6; //zoomed out\n\n        //only render if it forces it to do it\n        if (this.live_mode) {\n            if (!node.flags.collapsed) {\n                ctx.shadowColor = \"transparent\";\n                if (node.onDrawForeground) {\n                    node.onDrawForeground(ctx, this, this.canvas);\n                }\n            }\n            return;\n        }\n\n        var editor_alpha = this.editor_alpha;\n        ctx.globalAlpha = editor_alpha;\n\n        if (this.render_shadows && !low_quality) {\n            ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;\n            ctx.shadowOffsetX = 2 * this.ds.scale;\n            ctx.shadowOffsetY = 2 * this.ds.scale;\n            ctx.shadowBlur = 3 * this.ds.scale;\n        } else {\n            ctx.shadowColor = \"transparent\";\n        }\n\n        //custom draw collapsed method (draw after shadows because they are affected)\n        if (\n            node.flags.collapsed &&\n            node.onDrawCollapsed &&\n            node.onDrawCollapsed(ctx, this) == true\n        ) {\n            return;\n        }\n\n        //clip if required (mask)\n        var shape = node._shape || LiteGraph.BOX_SHAPE;\n        var size = temp_vec2;\n        temp_vec2.set(node.size);\n        var horizontal = node.horizontal; // || node.flags.horizontal;\n\n        if (node.flags.collapsed) {\n            ctx.font = this.inner_text_font;\n            var title = node.getTitle ? node.getTitle() : node.title;\n            if (title != null) {\n                node._collapsed_width = Math.min(\n                    node.size[0],\n                    ctx.measureText(title).width +\n                        LiteGraph.NODE_TITLE_HEIGHT * 2\n                ); //LiteGraph.NODE_COLLAPSED_WIDTH;\n                size[0] = node._collapsed_width;\n                size[1] = 0;\n            }\n        }\n\n        if (node.clip_area) {\n            //Start clipping\n            ctx.save();\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE) {\n                ctx.rect(0, 0, size[0], size[1]);\n            } else if (shape == LiteGraph.ROUND_SHAPE) {\n                ctx.roundRect(0, 0, size[0], size[1], [10]);\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.clip();\n        }\n\n        //draw shape\n        if (node.has_errors) {\n            bgcolor = \"red\";\n        }\n        this.drawNodeShape(\n            node,\n            ctx,\n            size,\n            color,\n            bgcolor,\n            node.is_selected,\n            node.mouseOver\n        );\n        ctx.shadowColor = \"transparent\";\n\n        //draw foreground\n        if (node.onDrawForeground) {\n            node.onDrawForeground(ctx, this, this.canvas);\n        }\n\n        //connection slots\n        ctx.textAlign = horizontal ? \"center\" : \"left\";\n        ctx.font = this.inner_text_font;\n\n        var render_text = !low_quality;\n\n        var out_slot = this.connecting_output;\n        var in_slot = this.connecting_input;\n        ctx.lineWidth = 1;\n\n        var max_y = 0;\n        var slot_pos = new Float32Array(2); //to reuse\n\n        //render inputs and outputs\n        if (!node.flags.collapsed) {\n            //input connection slots\n            if (node.inputs) {\n                for (var i = 0; i < node.inputs.length; i++) {\n                    var slot = node.inputs[i];\n                    \n                    var slot_type = slot.type;\n                    var slot_shape = slot.shape;\n                    \n                    ctx.globalAlpha = editor_alpha;\n                    //change opacity of incompatible slots when dragging a connection\n                    if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) {\n                        ctx.globalAlpha = 0.4 * editor_alpha;\n                    }\n\n                    ctx.fillStyle =\n                        slot.link != null\n                            ? slot.color_on ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.input_on\n                            : slot.color_off ||\n                              this.default_connection_color_byTypeOff[slot_type] ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.input_off;\n\n                    var pos = node.getConnectionPos(true, i, slot_pos);\n                    pos[0] -= node.pos[0];\n                    pos[1] -= node.pos[1];\n                    if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) {\n                        max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n                    }\n\n                    ctx.beginPath();\n\n\t\t\t\t\tif (slot_type == \"array\"){\n                        slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead?\n                    }\n                    \n                    var doStroke = true;\n                    \n                    if (\n                        slot.type === LiteGraph.EVENT ||\n                        slot.shape === LiteGraph.BOX_SHAPE\n                    ) {\n                        if (horizontal) {\n                            ctx.rect(\n                                pos[0] - 5 + 0.5,\n                                pos[1] - 8 + 0.5,\n                                10,\n                                14\n                            );\n                        } else {\n                            ctx.rect(\n                                pos[0] - 6 + 0.5,\n                                pos[1] - 5 + 0.5,\n                                14,\n                                10\n                            );\n                        }\n                    } else if (slot_shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(pos[0] + 8, pos[1] + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else if (slot_shape === LiteGraph.GRID_SHAPE) {\n                        ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2);\n                        doStroke = false;\n                    } else {\n\t\t\t\t\t\tif(low_quality)\n\t                        ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster\n\t\t\t\t\t\telse\n\t                        ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);\n                    }\n                    ctx.fill();\n\n                    //render name\n                    if (render_text) {\n                        var text = slot.label != null ? slot.label : slot.name;\n                        if (text) {\n                            ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;\n                            if (horizontal || slot.dir == LiteGraph.UP) {\n                                ctx.fillText(text, pos[0], pos[1] - 10);\n                            } else {\n                                ctx.fillText(text, pos[0] + 10, pos[1] + 5);\n                            }\n                        }\n                    }\n                }\n            }\n\n            //output connection slots\n\n            ctx.textAlign = horizontal ? \"center\" : \"right\";\n            ctx.strokeStyle = \"black\";\n            if (node.outputs) {\n                for (var i = 0; i < node.outputs.length; i++) {\n                    var slot = node.outputs[i];\n                    \n                    var slot_type = slot.type;\n                    var slot_shape = slot.shape;\n                    \n                    //change opacity of incompatible slots when dragging a connection\n                    if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) {\n                        ctx.globalAlpha = 0.4 * editor_alpha;\n                    }\n                    \n                    var pos = node.getConnectionPos(false, i, slot_pos);\n                    pos[0] -= node.pos[0];\n                    pos[1] -= node.pos[1];\n                    if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) {\n                        max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n                    }\n\n                    ctx.fillStyle =\n                        slot.links && slot.links.length\n                            ? slot.color_on ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.output_on\n                            : slot.color_off ||\n                              this.default_connection_color_byTypeOff[slot_type] ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.output_off;\n                    ctx.beginPath();\n                    //ctx.rect( node.size[0] - 14,i*14,10,10);\n\n\t\t\t\t\tif (slot_type == \"array\"){\n                        slot_shape = LiteGraph.GRID_SHAPE;\n                    }\n                    \n                    var doStroke = true;\n                    \n                    if (\n                        slot_type === LiteGraph.EVENT ||\n                        slot_shape === LiteGraph.BOX_SHAPE\n                    ) {\n                        if (horizontal) {\n                            ctx.rect(\n                                pos[0] - 5 + 0.5,\n                                pos[1] - 8 + 0.5,\n                                10,\n                                14\n                            );\n                        } else {\n                            ctx.rect(\n                                pos[0] - 6 + 0.5,\n                                pos[1] - 5 + 0.5,\n                                14,\n                                10\n                            );\n                        }\n                    } else if (slot_shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(pos[0] + 8, pos[1] + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);\n                        ctx.closePath();\n                    }  else if (slot_shape === LiteGraph.GRID_SHAPE) {\n                        ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2);\n                        doStroke = false;\n                    } else {\n\t\t\t\t\t\tif(low_quality)\n\t                        ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 );\n\t\t\t\t\t\telse\n\t                        ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);\n                    }\n\n                    //trigger\n                    //if(slot.node_id != null && slot.slot == -1)\n                    //\tctx.fillStyle = \"#F85\";\n\n                    //if(slot.links != null && slot.links.length)\n                    ctx.fill();\n\t\t\t\t\tif(!low_quality && doStroke)\n\t                    ctx.stroke();\n\n                    //render output name\n                    if (render_text) {\n                        var text = slot.label != null ? slot.label : slot.name;\n                        if (text) {\n                            ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;\n                            if (horizontal || slot.dir == LiteGraph.DOWN) {\n                                ctx.fillText(text, pos[0], pos[1] - 8);\n                            } else {\n                                ctx.fillText(text, pos[0] - 10, pos[1] + 5);\n                            }\n                        }\n                    }\n                }\n            }\n\n            ctx.textAlign = \"left\";\n            ctx.globalAlpha = 1;\n\n            if (node.widgets) {\n\t\t\t\tvar widgets_y = max_y;\n                if (horizontal || node.widgets_up) {\n                    widgets_y = 2;\n                }\n\t\t\t\tif( node.widgets_start_y != null )\n                    widgets_y = node.widgets_start_y;\n                this.drawNodeWidgets(\n                    node,\n                    widgets_y,\n                    ctx,\n                    this.node_widget && this.node_widget[0] == node\n                        ? this.node_widget[1]\n                        : null\n                );\n            }\n        } else if (this.render_collapsed_slots) {\n            //if collapsed\n            var input_slot = null;\n            var output_slot = null;\n\n            //get first connected slot to render\n            if (node.inputs) {\n                for (var i = 0; i < node.inputs.length; i++) {\n                    var slot = node.inputs[i];\n                    if (slot.link == null) {\n                        continue;\n                    }\n                    input_slot = slot;\n                    break;\n                }\n            }\n            if (node.outputs) {\n                for (var i = 0; i < node.outputs.length; i++) {\n                    var slot = node.outputs[i];\n                    if (!slot.links || !slot.links.length) {\n                        continue;\n                    }\n                    output_slot = slot;\n                }\n            }\n\n            if (input_slot) {\n                var x = 0;\n                var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center\n                if (horizontal) {\n                    x = node._collapsed_width * 0.5;\n                    y = -LiteGraph.NODE_TITLE_HEIGHT;\n                }\n                ctx.fillStyle = \"#686\";\n                ctx.beginPath();\n                if (\n                    slot.type === LiteGraph.EVENT ||\n                    slot.shape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(x - 7 + 0.5, y - 4, 14, 8);\n                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(x + 8, y);\n                    ctx.lineTo(x + -4, y - 4);\n                    ctx.lineTo(x + -4, y + 4);\n                    ctx.closePath();\n                } else {\n                    ctx.arc(x, y, 4, 0, Math.PI * 2);\n                }\n                ctx.fill();\n            }\n\n            if (output_slot) {\n                var x = node._collapsed_width;\n                var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center\n                if (horizontal) {\n                    x = node._collapsed_width * 0.5;\n                    y = 0;\n                }\n                ctx.fillStyle = \"#686\";\n                ctx.strokeStyle = \"black\";\n                ctx.beginPath();\n                if (\n                    slot.type === LiteGraph.EVENT ||\n                    slot.shape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(x - 7 + 0.5, y - 4, 14, 8);\n                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(x + 6, y);\n                    ctx.lineTo(x - 6, y - 4);\n                    ctx.lineTo(x - 6, y + 4);\n                    ctx.closePath();\n                } else {\n                    ctx.arc(x, y, 4, 0, Math.PI * 2);\n                }\n                ctx.fill();\n                //ctx.stroke();\n            }\n        }\n\n        if (node.clip_area) {\n            ctx.restore();\n        }\n\n        ctx.globalAlpha = 1.0;\n    };\n\n\t//used by this.over_link_center\n\tLGraphCanvas.prototype.drawLinkTooltip = function( ctx, link )\n\t{\n\t\tvar pos = link._pos;\n\t\tctx.fillStyle = \"black\";\n\t\tctx.beginPath();\n\t\tctx.arc( pos[0], pos[1], 3, 0, Math.PI * 2 );\n\t\tctx.fill();\n\n\t\tif(link.data == null)\n\t\t\treturn;\n\n\t\tif(this.onDrawLinkTooltip)\n\t\t\tif( this.onDrawLinkTooltip(ctx,link,this) == true )\n\t\t\t\treturn;\n\n\t\tvar data = link.data;\n\t\tvar text = null;\n\n\t\tif( data.constructor === Number )\n\t\t\ttext = data.toFixed(2);\n\t\telse if( data.constructor === String )\n\t\t\ttext = \"\\\"\" + data + \"\\\"\";\n\t\telse if( data.constructor === Boolean )\n\t\t\ttext = String(data);\n\t\telse if (data.toToolTip)\n\t\t\ttext = data.toToolTip();\n\t\telse\n\t\t\ttext = \"[\" + data.constructor.name + \"]\";\n\n\t\tif(text == null)\n\t\t\treturn;\n\t\ttext = text.substr(0,30); //avoid weird\n\n\t\tctx.font = \"14px Courier New\";\n\t\tvar info = ctx.measureText(text);\n\t\tvar w = info.width + 20;\n\t\tvar h = 24;\n\t\tctx.shadowColor = \"black\";\n\t\tctx.shadowOffsetX = 2;\n\t\tctx.shadowOffsetY = 2;\n\t\tctx.shadowBlur = 3;\n\t\tctx.fillStyle = \"#454\";\n\t\tctx.beginPath();\n\t\tctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]);\n\t\tctx.moveTo( pos[0] - 10, pos[1] - 15 );\n\t\tctx.lineTo( pos[0] + 10, pos[1] - 15 );\n\t\tctx.lineTo( pos[0], pos[1] - 5 );\n\t\tctx.fill();\n        ctx.shadowColor = \"transparent\";\n\t\tctx.textAlign = \"center\";\n\t\tctx.fillStyle = \"#CEC\";\n\t\tctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3);\n\t}\n\n    /**\n     * draws the shape of the given node in the canvas\n     * @method drawNodeShape\n     **/\n    var tmp_area = new Float32Array(4);\n\n    LGraphCanvas.prototype.drawNodeShape = function(\n        node,\n        ctx,\n        size,\n        fgcolor,\n        bgcolor,\n        selected,\n        mouse_over\n    ) {\n        //bg rect\n        ctx.strokeStyle = fgcolor;\n        ctx.fillStyle = bgcolor;\n\n        var title_height = LiteGraph.NODE_TITLE_HEIGHT;\n        var low_quality = this.ds.scale < 0.5;\n\n        //render node area depending on shape\n        var shape =\n            node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;\n\n        var title_mode = node.constructor.title_mode;\n\n        var render_title = true;\n        if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) {\n            render_title = false;\n        } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) {\n            render_title = true;\n        }\n\n        var area = tmp_area;\n        area[0] = 0; //x\n        area[1] = render_title ? -title_height : 0; //y\n        area[2] = size[0] + 1; //w\n        area[3] = render_title ? size[1] + title_height : size[1]; //h\n\n        var old_alpha = ctx.globalAlpha;\n\n        //full node shape\n        //if(node.flags.collapsed)\n        {\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE || low_quality) {\n                ctx.fillRect(area[0], area[1], area[2], area[3]);\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                shape == LiteGraph.CARD_SHAPE\n            ) {\n                ctx.roundRect(\n                    area[0],\n                    area[1],\n                    area[2],\n                    area[3],\n                    shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] \n                );\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.fill();\n\n\t\t\t//separator\n\t\t\tif(!node.flags.collapsed && render_title)\n\t\t\t{\n\t\t\t\tctx.shadowColor = \"transparent\";\n\t\t\t\tctx.fillStyle = \"rgba(0,0,0,0.2)\";\n\t\t\t\tctx.fillRect(0, -1, area[2], 2);\n\t\t\t}\n        }\n        ctx.shadowColor = \"transparent\";\n\n        if (node.onDrawBackground) {\n            node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse );\n        }\n\n        //title bg (remember, it is rendered ABOVE the node)\n        if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) {\n            //title bar\n            if (node.onDrawTitleBar) {\n                node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor );\n            } else if (\n                title_mode != LiteGraph.TRANSPARENT_TITLE &&\n                (node.constructor.title_color || this.render_title_colored)\n            ) {\n                var title_color = node.constructor.title_color || fgcolor;\n\n                if (node.flags.collapsed) {\n                    ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;\n                }\n\n                //* gradient test\n                if (this.use_gradients) {\n                    var grad = LGraphCanvas.gradients[title_color];\n                    if (!grad) {\n                        grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0);\n                        grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException\n                        grad.addColorStop(1, \"#000\");\n                    }\n                    ctx.fillStyle = grad;\n                } else {\n                    ctx.fillStyle = title_color;\n                }\n\n                //ctx.globalAlpha = 0.5 * old_alpha;\n                ctx.beginPath();\n                if (shape == LiteGraph.BOX_SHAPE || low_quality) {\n                    ctx.rect(0, -title_height, size[0] + 1, title_height);\n                } else if (  shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) {\n                    ctx.roundRect(\n                        0,\n                        -title_height,\n                        size[0] + 1,\n                        title_height,\n                        node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0]\n                    );\n                }\n                ctx.fill();\n                ctx.shadowColor = \"transparent\";\n            }\n\n            var colState = false;\n            if (LiteGraph.node_box_coloured_by_mode){\n                if(LiteGraph.NODE_MODES_COLORS[node.mode]){\n                    colState = LiteGraph.NODE_MODES_COLORS[node.mode];\n                }\n            }\n            if (LiteGraph.node_box_coloured_when_on){\n                colState = node.action_triggered ? \"#FFF\" : (node.execute_triggered ? \"#AAA\" : colState);\n            }\n            \n            //title box\n            var box_size = 10;\n            if (node.onDrawTitleBox) {\n                node.onDrawTitleBox(ctx, title_height, size, this.ds.scale);\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                shape == LiteGraph.CIRCLE_SHAPE ||\n                shape == LiteGraph.CARD_SHAPE\n            ) {\n                if (low_quality) {\n                    ctx.fillStyle = \"black\";\n                    ctx.beginPath();\n                    ctx.arc(\n                        title_height * 0.5,\n                        title_height * -0.5,\n                        box_size * 0.5 + 1,\n                        0,\n                        Math.PI * 2\n                    );\n                    ctx.fill();\n                }\n                \n                ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR;\n\t\t\t\tif(low_quality)\n\t\t\t\t\tctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size  );\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.arc(\n\t\t\t\t\t\ttitle_height * 0.5,\n\t\t\t\t\t\ttitle_height * -0.5,\n\t\t\t\t\t\tbox_size * 0.5,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tMath.PI * 2\n\t\t\t\t\t);\n\t\t\t\t\tctx.fill();\n\t\t\t\t}\n            } else {\n                if (low_quality) {\n                    ctx.fillStyle = \"black\";\n                    ctx.fillRect(\n                        (title_height - box_size) * 0.5 - 1,\n                        (title_height + box_size) * -0.5 - 1,\n                        box_size + 2,\n                        box_size + 2\n                    );\n                }\n                ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR;\n                ctx.fillRect(\n                    (title_height - box_size) * 0.5,\n                    (title_height + box_size) * -0.5,\n                    box_size,\n                    box_size\n                );\n            }\n            ctx.globalAlpha = old_alpha;\n\n            //title text\n            if (node.onDrawTitleText) {\n                node.onDrawTitleText(\n                    ctx,\n                    title_height,\n                    size,\n                    this.ds.scale,\n                    this.title_text_font,\n                    selected\n                );\n            }\n            if (!low_quality) {\n                ctx.font = this.title_text_font;\n                var title = String(node.getTitle());\n                if (title) {\n                    if (selected) {\n                        ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR;\n                    } else {\n                        ctx.fillStyle =\n                            node.constructor.title_text_color ||\n                            this.node_title_color;\n                    }\n                    if (node.flags.collapsed) {\n                        ctx.textAlign = \"left\";\n                        var measure = ctx.measureText(title);\n                        ctx.fillText(\n                            title.substr(0,20), //avoid urls too long\n                            title_height,// + measure.width * 0.5,\n                            LiteGraph.NODE_TITLE_TEXT_Y - title_height\n                        );\n                        ctx.textAlign = \"left\";\n                    } else {\n                        ctx.textAlign = \"left\";\n                        ctx.fillText(\n                            title,\n                            title_height,\n                            LiteGraph.NODE_TITLE_TEXT_Y - title_height\n                        );\n                    }\n                }\n            }\n\n\t\t\t//subgraph box\n\t\t\tif (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) {\n\t\t\t\tvar w = LiteGraph.NODE_TITLE_HEIGHT;\n\t\t\t\tvar x = node.size[0] - w;\n\t\t\t\tvar over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 );\n\t\t\t\tctx.fillStyle = over ? \"#888\" : \"#555\";\n\t\t\t\tif( shape == LiteGraph.BOX_SHAPE || low_quality)\n\t\t\t\t\tctx.fillRect(x+2, -w+2, w-4, w-4);\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.roundRect(x+2, -w+2, w-4, w-4,[4]);\n\t\t\t\t\tctx.fill();\n\t\t\t\t}\n\t\t\t\tctx.fillStyle = \"#333\";\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.moveTo(x + w * 0.2, -w * 0.6);\n\t\t\t\tctx.lineTo(x + w * 0.8, -w * 0.6);\n\t\t\t\tctx.lineTo(x + w * 0.5, -w * 0.3);\n\t\t\t\tctx.fill();\n\t\t\t}\n\n\t\t\t//custom title render\n            if (node.onDrawTitle) {\n                node.onDrawTitle(ctx);\n            }\n        }\n\n        //render selection marker\n        if (selected) {\n            if (node.onBounding) {\n                node.onBounding(area);\n            }\n\n            if (title_mode == LiteGraph.TRANSPARENT_TITLE) {\n                area[1] -= title_height;\n                area[3] += title_height;\n            }\n            ctx.lineWidth = 1;\n            ctx.globalAlpha = 0.8;\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE) {\n                ctx.rect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3]\n                );\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed)\n            ) {\n                ctx.roundRect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3],\n                    [this.round_radius * 2]\n                );\n            } else if (shape == LiteGraph.CARD_SHAPE) {\n                ctx.roundRect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3],\n                    [this.round_radius * 2,2,this.round_radius * 2,2]\n                );\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5 + 6,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR;\n            ctx.stroke();\n            ctx.strokeStyle = fgcolor;\n            ctx.globalAlpha = 1;\n        }\n        \n        // these counter helps in conditioning drawing based on if the node has been executed or an action occurred\n        if (node.execute_triggered>0) node.execute_triggered--;\n        if (node.action_triggered>0) node.action_triggered--;\n    };\n\n    var margin_area = new Float32Array(4);\n    var link_bounding = new Float32Array(4);\n    var tempA = new Float32Array(2);\n    var tempB = new Float32Array(2);\n\n    /**\n     * draws every connection visible in the canvas\n     * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time\n     * @method drawConnections\n     **/\n    LGraphCanvas.prototype.drawConnections = function(ctx) {\n        var now = LiteGraph.getTime();\n        var visible_area = this.visible_area;\n        margin_area[0] = visible_area[0] - 20;\n        margin_area[1] = visible_area[1] - 20;\n        margin_area[2] = visible_area[2] + 40;\n        margin_area[3] = visible_area[3] + 40;\n\n        //draw connections\n        ctx.lineWidth = this.connections_width;\n\n        ctx.fillStyle = \"#AAA\";\n        ctx.strokeStyle = \"#AAA\";\n        ctx.globalAlpha = this.editor_alpha;\n        //for every node\n        var nodes = this.graph._nodes;\n        for (var n = 0, l = nodes.length; n < l; ++n) {\n            var node = nodes[n];\n            //for every input (we render just inputs because it is easier as every slot can only have one input)\n            if (!node.inputs || !node.inputs.length) {\n                continue;\n            }\n\n            for (var i = 0; i < node.inputs.length; ++i) {\n                var input = node.inputs[i];\n                if (!input || input.link == null) {\n                    continue;\n                }\n                var link_id = input.link;\n                var link = this.graph.links[link_id];\n                if (!link) {\n                    continue;\n                }\n\n                //find link info\n                var start_node = this.graph.getNodeById(link.origin_id);\n                if (start_node == null) {\n                    continue;\n                }\n                var start_node_slot = link.origin_slot;\n                var start_node_slotpos = null;\n                if (start_node_slot == -1) {\n                    start_node_slotpos = [\n                        start_node.pos[0] + 10,\n                        start_node.pos[1] + 10\n                    ];\n                } else {\n                    start_node_slotpos = start_node.getConnectionPos(\n                        false,\n                        start_node_slot,\n                        tempA\n                    );\n                }\n                var end_node_slotpos = node.getConnectionPos(true, i, tempB);\n\n                //compute link bounding\n                link_bounding[0] = start_node_slotpos[0];\n                link_bounding[1] = start_node_slotpos[1];\n                link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0];\n                link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1];\n                if (link_bounding[2] < 0) {\n                    link_bounding[0] += link_bounding[2];\n                    link_bounding[2] = Math.abs(link_bounding[2]);\n                }\n                if (link_bounding[3] < 0) {\n                    link_bounding[1] += link_bounding[3];\n                    link_bounding[3] = Math.abs(link_bounding[3]);\n                }\n\n                //skip links outside of the visible area of the canvas\n                if (!overlapBounding(link_bounding, margin_area)) {\n                    continue;\n                }\n\n                var start_slot = start_node.outputs[start_node_slot];\n                var end_slot = node.inputs[i];\n                if (!start_slot || !end_slot) {\n                    continue;\n                }\n                var start_dir =\n                    start_slot.dir ||\n                    (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT);\n                var end_dir =\n                    end_slot.dir ||\n                    (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT);\n\n                this.renderLink(\n                    ctx,\n                    start_node_slotpos,\n                    end_node_slotpos,\n                    link,\n                    false,\n                    0,\n                    null,\n                    start_dir,\n                    end_dir\n                );\n\n                //event triggered rendered on top\n                if (link && link._last_time && now - link._last_time < 1000) {\n                    var f = 2.0 - (now - link._last_time) * 0.002;\n                    var tmp = ctx.globalAlpha;\n                    ctx.globalAlpha = tmp * f;\n                    this.renderLink(\n                        ctx,\n                        start_node_slotpos,\n                        end_node_slotpos,\n                        link,\n                        true,\n                        f,\n                        \"white\",\n                        start_dir,\n                        end_dir\n                    );\n                    ctx.globalAlpha = tmp;\n                }\n            }\n        }\n        ctx.globalAlpha = 1;\n    };\n\n    /**\n     * draws a link between two points\n     * @method renderLink\n     * @param {vec2} a start pos\n     * @param {vec2} b end pos\n     * @param {Object} link the link object with all the link info\n     * @param {boolean} skip_border ignore the shadow of the link\n     * @param {boolean} flow show flow animation (for events)\n     * @param {string} color the color for the link\n     * @param {number} start_dir the direction enum\n     * @param {number} end_dir the direction enum\n     * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb)\n     **/\n    LGraphCanvas.prototype.renderLink = function(\n        ctx,\n        a,\n        b,\n        link,\n        skip_border,\n        flow,\n        color,\n        start_dir,\n        end_dir,\n        num_sublines\n    ) {\n        if (link) {\n            this.visible_links.push(link);\n        }\n\n        //choose color\n        if (!color && link) {\n            color = link.color || LGraphCanvas.link_type_colors[link.type];\n        }\n        if (!color) {\n            color = this.default_link_color;\n        }\n        if (link != null && this.highlighted_links[link.id]) {\n            color = \"#FFF\";\n        }\n\n        start_dir = start_dir || LiteGraph.RIGHT;\n        end_dir = end_dir || LiteGraph.LEFT;\n\n        var dist = distance(a, b);\n\n        if (this.render_connections_border && this.ds.scale > 0.6) {\n            ctx.lineWidth = this.connections_width + 4;\n        }\n        ctx.lineJoin = \"round\";\n        num_sublines = num_sublines || 1;\n        if (num_sublines > 1) {\n            ctx.lineWidth = 0.5;\n        }\n\n        //begin line shape\n        ctx.beginPath();\n        for (var i = 0; i < num_sublines; i += 1) {\n            var offsety = (i - (num_sublines - 1) * 0.5) * 5;\n\n            if (this.links_render_mode == LiteGraph.SPLINE_LINK) {\n                ctx.moveTo(a[0], a[1] + offsety);\n                var start_offset_x = 0;\n                var start_offset_y = 0;\n                var end_offset_x = 0;\n                var end_offset_y = 0;\n                switch (start_dir) {\n                    case LiteGraph.LEFT:\n                        start_offset_x = dist * -0.25;\n                        break;\n                    case LiteGraph.RIGHT:\n                        start_offset_x = dist * 0.25;\n                        break;\n                    case LiteGraph.UP:\n                        start_offset_y = dist * -0.25;\n                        break;\n                    case LiteGraph.DOWN:\n                        start_offset_y = dist * 0.25;\n                        break;\n                }\n                switch (end_dir) {\n                    case LiteGraph.LEFT:\n                        end_offset_x = dist * -0.25;\n                        break;\n                    case LiteGraph.RIGHT:\n                        end_offset_x = dist * 0.25;\n                        break;\n                    case LiteGraph.UP:\n                        end_offset_y = dist * -0.25;\n                        break;\n                    case LiteGraph.DOWN:\n                        end_offset_y = dist * 0.25;\n                        break;\n                }\n                ctx.bezierCurveTo(\n                    a[0] + start_offset_x,\n                    a[1] + start_offset_y + offsety,\n                    b[0] + end_offset_x,\n                    b[1] + end_offset_y + offsety,\n                    b[0],\n                    b[1] + offsety\n                );\n            } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) {\n                ctx.moveTo(a[0], a[1] + offsety);\n                var start_offset_x = 0;\n                var start_offset_y = 0;\n                var end_offset_x = 0;\n                var end_offset_y = 0;\n                switch (start_dir) {\n                    case LiteGraph.LEFT:\n                        start_offset_x = -1;\n                        break;\n                    case LiteGraph.RIGHT:\n                        start_offset_x = 1;\n                        break;\n                    case LiteGraph.UP:\n                        start_offset_y = -1;\n                        break;\n                    case LiteGraph.DOWN:\n                        start_offset_y = 1;\n                        break;\n                }\n                switch (end_dir) {\n                    case LiteGraph.LEFT:\n                        end_offset_x = -1;\n                        break;\n                    case LiteGraph.RIGHT:\n                        end_offset_x = 1;\n                        break;\n                    case LiteGraph.UP:\n                        end_offset_y = -1;\n                        break;\n                    case LiteGraph.DOWN:\n                        end_offset_y = 1;\n                        break;\n                }\n                var l = 15;\n                ctx.lineTo(\n                    a[0] + start_offset_x * l,\n                    a[1] + start_offset_y * l + offsety\n                );\n                ctx.lineTo(\n                    b[0] + end_offset_x * l,\n                    b[1] + end_offset_y * l + offsety\n                );\n                ctx.lineTo(b[0], b[1] + offsety);\n            } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) {\n                ctx.moveTo(a[0], a[1]);\n                var start_x = a[0];\n                var start_y = a[1];\n                var end_x = b[0];\n                var end_y = b[1];\n                if (start_dir == LiteGraph.RIGHT) {\n                    start_x += 10;\n                } else {\n                    start_y += 10;\n                }\n                if (end_dir == LiteGraph.LEFT) {\n                    end_x -= 10;\n                } else {\n                    end_y -= 10;\n                }\n                ctx.lineTo(start_x, start_y);\n                ctx.lineTo((start_x + end_x) * 0.5, start_y);\n                ctx.lineTo((start_x + end_x) * 0.5, end_y);\n                ctx.lineTo(end_x, end_y);\n                ctx.lineTo(b[0], b[1]);\n            } else {\n                return;\n            } //unknown\n        }\n\n        //rendering the outline of the connection can be a little bit slow\n        if (\n            this.render_connections_border &&\n            this.ds.scale > 0.6 &&\n            !skip_border\n        ) {\n            ctx.strokeStyle = \"rgba(0,0,0,0.5)\";\n            ctx.stroke();\n        }\n\n        ctx.lineWidth = this.connections_width;\n        ctx.fillStyle = ctx.strokeStyle = color;\n        ctx.stroke();\n        //end line shape\n\n        var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir);\n        if (link && link._pos) {\n            link._pos[0] = pos[0];\n            link._pos[1] = pos[1];\n        }\n\n        //render arrow in the middle\n        if (\n            this.ds.scale >= 0.6 &&\n            this.highquality_render &&\n            end_dir != LiteGraph.CENTER\n        ) {\n            //render arrow\n            if (this.render_connection_arrows) {\n                //compute two points in the connection\n                var posA = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.25,\n                    start_dir,\n                    end_dir\n                );\n                var posB = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.26,\n                    start_dir,\n                    end_dir\n                );\n                var posC = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.75,\n                    start_dir,\n                    end_dir\n                );\n                var posD = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.76,\n                    start_dir,\n                    end_dir\n                );\n\n                //compute the angle between them so the arrow points in the right direction\n                var angleA = 0;\n                var angleB = 0;\n                if (this.render_curved_connections) {\n                    angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]);\n                    angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]);\n                } else {\n                    angleB = angleA = b[1] > a[1] ? 0 : Math.PI;\n                }\n\n                //render arrow\n                ctx.save();\n                ctx.translate(posA[0], posA[1]);\n                ctx.rotate(angleA);\n                ctx.beginPath();\n                ctx.moveTo(-5, -3);\n                ctx.lineTo(0, +7);\n                ctx.lineTo(+5, -3);\n                ctx.fill();\n                ctx.restore();\n                ctx.save();\n                ctx.translate(posC[0], posC[1]);\n                ctx.rotate(angleB);\n                ctx.beginPath();\n                ctx.moveTo(-5, -3);\n                ctx.lineTo(0, +7);\n                ctx.lineTo(+5, -3);\n                ctx.fill();\n                ctx.restore();\n            }\n\n            //circle\n            ctx.beginPath();\n            ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2);\n            ctx.fill();\n        }\n\n        //render flowing points\n        if (flow) {\n            ctx.fillStyle = color;\n            for (var i = 0; i < 5; ++i) {\n                var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1;\n                var pos = this.computeConnectionPoint(\n                    a,\n                    b,\n                    f,\n                    start_dir,\n                    end_dir\n                );\n                ctx.beginPath();\n                ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI);\n                ctx.fill();\n            }\n        }\n    };\n\n    //returns the link center point based on curvature\n    LGraphCanvas.prototype.computeConnectionPoint = function(\n        a,\n        b,\n        t,\n        start_dir,\n        end_dir\n    ) {\n        start_dir = start_dir || LiteGraph.RIGHT;\n        end_dir = end_dir || LiteGraph.LEFT;\n\n        var dist = distance(a, b);\n        var p0 = a;\n        var p1 = [a[0], a[1]];\n        var p2 = [b[0], b[1]];\n        var p3 = b;\n\n        switch (start_dir) {\n            case LiteGraph.LEFT:\n                p1[0] += dist * -0.25;\n                break;\n            case LiteGraph.RIGHT:\n                p1[0] += dist * 0.25;\n                break;\n            case LiteGraph.UP:\n                p1[1] += dist * -0.25;\n                break;\n            case LiteGraph.DOWN:\n                p1[1] += dist * 0.25;\n                break;\n        }\n        switch (end_dir) {\n            case LiteGraph.LEFT:\n                p2[0] += dist * -0.25;\n                break;\n            case LiteGraph.RIGHT:\n                p2[0] += dist * 0.25;\n                break;\n            case LiteGraph.UP:\n                p2[1] += dist * -0.25;\n                break;\n            case LiteGraph.DOWN:\n                p2[1] += dist * 0.25;\n                break;\n        }\n\n        var c1 = (1 - t) * (1 - t) * (1 - t);\n        var c2 = 3 * ((1 - t) * (1 - t)) * t;\n        var c3 = 3 * (1 - t) * (t * t);\n        var c4 = t * t * t;\n\n        var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0];\n        var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1];\n        return [x, y];\n    };\n\n    LGraphCanvas.prototype.drawExecutionOrder = function(ctx) {\n        ctx.shadowColor = \"transparent\";\n        ctx.globalAlpha = 0.25;\n\n        ctx.textAlign = \"center\";\n        ctx.strokeStyle = \"white\";\n        ctx.globalAlpha = 0.75;\n\n        var visible_nodes = this.visible_nodes;\n        for (var i = 0; i < visible_nodes.length; ++i) {\n            var node = visible_nodes[i];\n            ctx.fillStyle = \"black\";\n            ctx.fillRect(\n                node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT,\n                node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT,\n                LiteGraph.NODE_TITLE_HEIGHT,\n                LiteGraph.NODE_TITLE_HEIGHT\n            );\n            if (node.order == 0) {\n                ctx.strokeRect(\n                    node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,\n                    node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,\n                    LiteGraph.NODE_TITLE_HEIGHT,\n                    LiteGraph.NODE_TITLE_HEIGHT\n                );\n            }\n            ctx.fillStyle = \"#FFF\";\n            ctx.fillText(\n                node.order,\n                node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5,\n                node.pos[1] - 6\n            );\n        }\n        ctx.globalAlpha = 1;\n    };\n\n    /**\n     * draws the widgets stored inside a node\n     * @method drawNodeWidgets\n     **/\n    LGraphCanvas.prototype.drawNodeWidgets = function(\n        node,\n        posY,\n        ctx,\n        active_widget\n    ) {\n        if (!node.widgets || !node.widgets.length) {\n            return 0;\n        }\n        var width = node.size[0];\n        var widgets = node.widgets;\n        posY += 2;\n        var H = LiteGraph.NODE_WIDGET_HEIGHT;\n        var show_text = this.ds.scale > 0.5;\n        ctx.save();\n        ctx.globalAlpha = this.editor_alpha;\n        var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR;\n        var background_color = LiteGraph.WIDGET_BGCOLOR;\n        var text_color = LiteGraph.WIDGET_TEXT_COLOR;\n\t\tvar secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;\n        var margin = 15;\n\n        for (var i = 0; i < widgets.length; ++i) {\n            var w = widgets[i];\n            var y = posY;\n            if (w.y) {\n                y = w.y;\n            }\n            w.last_y = y;\n            ctx.strokeStyle = outline_color;\n            ctx.fillStyle = \"#222\";\n            ctx.textAlign = \"left\";\n\t\t\t//ctx.lineWidth = 2;\n\t\t\tif(w.disabled)\n\t\t\t\tctx.globalAlpha *= 0.5;\n\t\t\tvar widget_width = w.width || width;\n\n            switch (w.type) {\n                case \"button\":\n                    if (w.clicked) {\n                        ctx.fillStyle = \"#AAA\";\n                        w.clicked = false;\n                        this.dirty_canvas = true;\n                    }\n                    ctx.fillRect(margin, y, widget_width - margin * 2, H);\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.strokeRect( margin, y, widget_width - margin * 2, H );\n                    if (show_text) {\n                        ctx.textAlign = \"center\";\n                        ctx.fillStyle = text_color;\n                        ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7);\n                    }\n                    break;\n                case \"toggle\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n                    if (show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]);\n\t\t\t\t\telse\n\t                    ctx.rect(margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.stroke();\n                    ctx.fillStyle = w.value ? \"#89A\" : \"#333\";\n                    ctx.beginPath();\n                    ctx.arc( widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2 );\n                    ctx.fill();\n                    if (show_text) {\n                        ctx.fillStyle = secondary_text_color;\n                        const label = w.label || w.name;    \n                        if (label != null) {\n                            ctx.fillText(label, margin * 2, y + H * 0.7);\n                        }\n                        ctx.fillStyle = w.value ? text_color : secondary_text_color;\n                        ctx.textAlign = \"right\";\n                        ctx.fillText(\n                            w.value\n                                ? w.options.on || \"true\"\n                                : w.options.off || \"false\",\n                            widget_width - 40,\n                            y + H * 0.7\n                        );\n                    }\n                    break;\n                case \"slider\":\n                    ctx.fillStyle = background_color;\n                    ctx.fillRect(margin, y, widget_width - margin * 2, H);\n                    var range = w.options.max - w.options.min;\n                    var nvalue = (w.value - w.options.min) / range;\n\t\t\t\t\tif(nvalue < 0.0) nvalue = 0.0;\n\t\t\t\t\tif(nvalue > 1.0) nvalue = 1.0;\n                    ctx.fillStyle = w.options.hasOwnProperty(\"slider_color\") ? w.options.slider_color : (active_widget == w ? \"#89A\" : \"#678\");\n                    ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H);\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.strokeRect(margin, y, widget_width - margin * 2, H);\n                    if (w.marker) {\n                        var marker_nvalue = (w.marker - w.options.min) / range;\n\t\t\t\t\t\tif(marker_nvalue < 0.0) marker_nvalue = 0.0;\n\t\t\t\t\t\tif(marker_nvalue > 1.0) marker_nvalue = 1.0;\n                        ctx.fillStyle = w.options.hasOwnProperty(\"marker_color\") ? w.options.marker_color : \"#AA9\";\n                        ctx.fillRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H );\n                    }\n                    if (show_text) {\n                        ctx.textAlign = \"center\";\n                        ctx.fillStyle = text_color;\n                        ctx.fillText(\n                            w.label || w.name + \"  \" + Number(w.value).toFixed(\n                                                            w.options.precision != null\n                                                                ? w.options.precision\n                                                                : 3\n                                                        ),\n                            widget_width * 0.5,\n                            y + H * 0.7\n                        );\n                    }\n                    break;\n                case \"number\":\n                case \"combo\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n\t\t\t\t\tif(show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5] );\n\t\t\t\t\telse\n\t                    ctx.rect(margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n                    if (show_text) {\n\t\t\t\t\t\tif(!w.disabled)\n\t\t                    ctx.stroke();\n                        ctx.fillStyle = text_color;\n\t\t\t\t\t\tif(!w.disabled)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\t\tctx.moveTo(margin + 16, y + 5);\n\t\t\t\t\t\t\tctx.lineTo(margin + 6, y + H * 0.5);\n\t\t\t\t\t\t\tctx.lineTo(margin + 16, y + H - 5);\n\t\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\t\tctx.moveTo(widget_width - margin - 16, y + 5);\n\t\t\t\t\t\t\tctx.lineTo(widget_width - margin - 6, y + H * 0.5);\n\t\t\t\t\t\t\tctx.lineTo(widget_width - margin - 16, y + H - 5);\n\t\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\t}\n                        ctx.fillStyle = secondary_text_color;\n                        ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7);\n                        ctx.fillStyle = text_color;\n                        ctx.textAlign = \"right\";\n                        if (w.type == \"number\") {\n                            ctx.fillText(\n                                Number(w.value).toFixed(\n                                    w.options.precision !== undefined\n                                        ? w.options.precision\n                                        : 3\n                                ),\n                                widget_width - margin * 2 - 20,\n                                y + H * 0.7\n                            );\n                        } else {\n\t\t\t\t\t\t\tvar v = w.value;\n\t\t\t\t\t\t\tif( w.options.values )\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tvar values = w.options.values;\n\t\t\t\t\t\t\t\tif( values.constructor === Function )\n\t\t\t\t\t\t\t\t\tvalues = values();\n\t\t\t\t\t\t\t\tif(values && values.constructor !== Array)\n\t\t\t\t\t\t\t\t\tv = values[ w.value ];\n\t\t\t\t\t\t\t}\n                            ctx.fillText(\n                                v,\n                                widget_width - margin * 2 - 20,\n                                y + H * 0.7\n                            );\n                        }\n                    }\n                    break;\n                case \"string\":\n                case \"text\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n                    if (show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]);\n\t\t\t\t\telse\n\t                    ctx.rect( margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n\t                if (show_text) {\n\t\t\t\t\t\tif(!w.disabled)\n\t\t\t\t\t\t\tctx.stroke();\n    \t\t\t\t\tctx.save();\n\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\tctx.rect(margin, y, widget_width - margin * 2, H);\n\t\t\t\t\t\tctx.clip();\n\n\t                    //ctx.stroke();\n                        ctx.fillStyle = secondary_text_color;\n                        const label = w.label || w.name;\t\n                        if (label != null) {\n                            ctx.fillText(label, margin * 2, y + H * 0.7);\n                        }\n                        ctx.fillStyle = text_color;\n                        ctx.textAlign = \"right\";\n                        ctx.fillText(String(w.value).substr(0,30), widget_width - margin * 2, y + H * 0.7); //30 chars max\n\t\t\t\t\t\tctx.restore();\n                    }\n                    break;\n                default:\n                    if (w.draw) {\n                        w.draw(ctx, node, widget_width, y, H);\n                    }\n                    break;\n            }\n            posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4;\n\t\t\tctx.globalAlpha = this.editor_alpha;\n\n        }\n        ctx.restore();\n\t\tctx.textAlign = \"left\";\n    };\n\n    /**\n     * process an event on widgets\n     * @method processNodeWidgets\n     **/\n    LGraphCanvas.prototype.processNodeWidgets = function(\n        node,\n        pos,\n        event,\n        active_widget\n    ) {\n        if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) {\n            return null;\n        }\n\n        var x = pos[0] - node.pos[0];\n        var y = pos[1] - node.pos[1];\n        var width = node.size[0];\n        var deltaX = event.deltaX || event.deltax || 0;\n        var that = this;\n        var ref_window = this.getCanvasWindow();\n\n        for (var i = 0; i < node.widgets.length; ++i) {\n            var w = node.widgets[i];\n\t\t\tif(!w || w.disabled)\n\t\t\t\tcontinue;\n\t\t\tvar widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT;\n\t\t\tvar widget_width = w.width || width;\n\t\t\t//outside\n\t\t\tif ( w != active_widget && \n\t\t\t\t(x < 6 || x > widget_width - 12 || y < w.last_y || y > w.last_y + widget_height || w.last_y === undefined) ) \n\t\t\t\tcontinue;\n\n\t\t\tvar old_value = w.value;\n\n            //if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) {\n\t\t\t//inside widget\n\t\t\tswitch (w.type) {\n\t\t\t\tcase \"button\":\n\t\t\t\t\tif (event.type === LiteGraph.pointerevents_method+\"down\") {\n                        if (w.callback) {\n                            setTimeout(function() {\n                                w.callback(w, that, node, pos, event);\n                            }, 20);\n                        }\n                        w.clicked = true;\n                        this.dirty_canvas = true;\n                    }\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"slider\":\n\t\t\t\t\tvar old_value = w.value;\n\t\t\t\t\tvar nvalue = clamp((x - 15) / (widget_width - 30), 0, 1);\n\t\t\t\t\tif(w.options.read_only) break;\n\t\t\t\t\tw.value = w.options.min + (w.options.max - w.options.min) * nvalue;\n\t\t\t\t\tif (old_value != w.value) {\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tinner_value_change(w, w.value);\n\t\t\t\t\t\t}, 20);\n\t\t\t\t\t}\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"number\":\n\t\t\t\tcase \"combo\":\n\t\t\t\t\tvar old_value = w.value;\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"move\" && w.type == \"number\") {\n                        if(deltaX)\n\t\t\t\t\t\t    w.value += deltaX * 0.1 * (w.options.step || 1);\n\t\t\t\t\t\tif ( w.options.min != null && w.value < w.options.min ) {\n\t\t\t\t\t\t\tw.value = w.options.min;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( w.options.max != null && w.value > w.options.max ) {\n\t\t\t\t\t\t\tw.value = w.options.max;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tvar values = w.options.values;\n\t\t\t\t\t\tif (values && values.constructor === Function) {\n\t\t\t\t\t\t\tvalues = w.options.values(w, node);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar values_list = null;\n\t\t\t\t\t\t\n\t\t\t\t\t\tif( w.type != \"number\")\n\t\t\t\t\t\t\tvalues_list = values.constructor === Array ? values : Object.keys(values);\n\n\t\t\t\t\t\tvar delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0;\n\t\t\t\t\t\tif (w.type == \"number\") {\n\t\t\t\t\t\t\tw.value += delta * 0.1 * (w.options.step || 1);\n\t\t\t\t\t\t\tif ( w.options.min != null && w.value < w.options.min ) {\n\t\t\t\t\t\t\t\tw.value = w.options.min;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ( w.options.max != null && w.value > w.options.max ) {\n\t\t\t\t\t\t\t\tw.value = w.options.max;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (delta) { //clicked in arrow, used for combos \n\t\t\t\t\t\t\tvar index = -1;\n\t\t\t\t\t\t\tthis.last_mouseclick = 0; //avoids dobl click event\n\t\t\t\t\t\t\tif(values.constructor === Object)\n\t\t\t\t\t\t\t\tindex = values_list.indexOf( String( w.value ) ) + delta;\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tindex = values_list.indexOf( w.value ) + delta;\n\t\t\t\t\t\t\tif (index >= values_list.length) {\n\t\t\t\t\t\t\t\tindex = values_list.length - 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (index < 0) {\n\t\t\t\t\t\t\t\tindex = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif( values.constructor === Array )\n\t\t\t\t\t\t\t\tw.value = values[index];\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tw.value = index;\n\t\t\t\t\t\t} else { //combo clicked \n\t\t\t\t\t\t\tvar text_values = values != values_list ? Object.values(values) : values;\n\t\t\t\t\t\t\tvar menu = new LiteGraph.ContextMenu(text_values, {\n\t\t\t\t\t\t\t\t\tscale: Math.max(1, this.ds.scale),\n\t\t\t\t\t\t\t\t\tevent: event,\n\t\t\t\t\t\t\t\t\tclassName: \"dark\",\n\t\t\t\t\t\t\t\t\tcallback: inner_clicked.bind(w)\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tref_window);\n\t\t\t\t\t\t\tfunction inner_clicked(v, option, event) {\n\t\t\t\t\t\t\t\tif(values != values_list)\n\t\t\t\t\t\t\t\t\tv = text_values.indexOf(v);\n\t\t\t\t\t\t\t\tthis.value = v;\n\t\t\t\t\t\t\t\tinner_value_change(this, v);\n\t\t\t\t\t\t\t\tthat.dirty_canvas = true;\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} //end mousedown\n\t\t\t\t\telse if(event.type == LiteGraph.pointerevents_method+\"up\" && w.type == \"number\")\n\t\t\t\t\t{\n\t\t\t\t\t\tvar delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0;\n\t\t\t\t\t\tif (event.click_time < 200 && delta == 0) {\n\t\t\t\t\t\t\tthis.prompt(\"Value\",w.value,function(v) {\n\t\t\t\t\t\t\t\t\t// check if v is a valid equation or a number\n\t\t\t\t\t\t\t\t\t  if (/^[0-9+\\-*/()\\s]+|\\d+\\.\\d+$/.test(v)) {\n\t\t\t\t\t\t\t\t\t\ttry {//solve the equation if possible\n\t\t\t\t\t\t\t\t\t    \t\tv = eval(v);\n\t\t\t\t\t\t\t\t\t\t} catch (e) { }\n\t\t\t\t\t\t\t\t\t}\t\n\t\t\t\t\t\t\t\t\tthis.value = Number(v);\n\t\t\t\t\t\t\t\t\tinner_value_change(this, this.value);\n\t\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\t\tevent);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif( old_value != w.value )\n\t\t\t\t\t\tsetTimeout(\n\t\t\t\t\t\t\tfunction() {\n\t\t\t\t\t\t\t\tinner_value_change(this, this.value);\n\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\t20\n\t\t\t\t\t\t);\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"toggle\":\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tw.value = !w.value;\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tinner_value_change(w, w.value);\n\t\t\t\t\t\t}, 20);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"string\":\n\t\t\t\tcase \"text\":\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tthis.prompt(\"Value\",w.value,function(v) {\n\t\t\t\t\t\t\t\tinner_value_change(this, v);\n\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\tevent,w.options ? w.options.multiline : false );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tif (w.mouse) {\n\t\t\t\t\t\tthis.dirty_canvas = w.mouse(event, [x, y], node);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t} //end switch\n\n\t\t\t//value changed\n\t\t\tif( old_value != w.value )\n\t\t\t{\n\t\t\t\tif(node.onWidgetChanged)\n\t\t\t\t\tnode.onWidgetChanged( w.name,w.value,old_value,w );\n                node.graph._version++;\n\t\t\t}\n\n\t\t\treturn w;\n        }//end for\n\n        function inner_value_change(widget, value) {\n            if(widget.type == \"number\"){\n                value = Number(value);\n            }\n            widget.value = value;\n            if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) {\n                node.setProperty( widget.options.property, value );\n            }\n            if (widget.callback) {\n                widget.callback(widget.value, that, node, pos, event);\n            }\n        }\n\n        return null;\n    };\n\n    /**\n     * draws every group area in the background\n     * @method drawGroups\n     **/\n    LGraphCanvas.prototype.drawGroups = function(canvas, ctx) {\n        if (!this.graph) {\n            return;\n        }\n\n        var groups = this.graph._groups;\n\n        ctx.save();\n        ctx.globalAlpha = 0.5 * this.editor_alpha;\n\n        for (var i = 0; i < groups.length; ++i) {\n            var group = groups[i];\n\n            if (!overlapBounding(this.visible_area, group._bounding)) {\n                continue;\n            } //out of the visible area\n\n            ctx.fillStyle = group.color || \"#335\";\n            ctx.strokeStyle = group.color || \"#335\";\n            var pos = group._pos;\n            var size = group._size;\n            ctx.globalAlpha = 0.25 * this.editor_alpha;\n            ctx.beginPath();\n            ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]);\n            ctx.fill();\n            ctx.globalAlpha = this.editor_alpha;\n            ctx.stroke();\n\n            ctx.beginPath();\n            ctx.moveTo(pos[0] + size[0], pos[1] + size[1]);\n            ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]);\n            ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10);\n            ctx.fill();\n\n            var font_size =\n                group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;\n            ctx.font = font_size + \"px Arial\";\n\t\t\tctx.textAlign = \"left\";\n            ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size);\n        }\n\n        ctx.restore();\n    };\n\n    LGraphCanvas.prototype.adjustNodesSize = function() {\n        var nodes = this.graph._nodes;\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].size = nodes[i].computeSize();\n        }\n        this.setDirty(true, true);\n    };\n\n    /**\n     * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode\n     * @method resize\n     **/\n    LGraphCanvas.prototype.resize = function(width, height) {\n        if (!width && !height) {\n            var parent = this.canvas.parentNode;\n            width = parent.offsetWidth;\n            height = parent.offsetHeight;\n        }\n\n        if (this.canvas.width == width && this.canvas.height == height) {\n            return;\n        }\n\n        this.canvas.width = width;\n        this.canvas.height = height;\n        this.bgcanvas.width = this.canvas.width;\n        this.bgcanvas.height = this.canvas.height;\n        this.setDirty(true, true);\n    };\n\n    /**\n     * switches to live mode (node shapes are not rendered, only the content)\n     * this feature was designed when graphs where meant to create user interfaces\n     * @method switchLiveMode\n     **/\n    LGraphCanvas.prototype.switchLiveMode = function(transition) {\n        if (!transition) {\n            this.live_mode = !this.live_mode;\n            this.dirty_canvas = true;\n            this.dirty_bgcanvas = true;\n            return;\n        }\n\n        var self = this;\n        var delta = this.live_mode ? 1.1 : 0.9;\n        if (this.live_mode) {\n            this.live_mode = false;\n            this.editor_alpha = 0.1;\n        }\n\n        var t = setInterval(function() {\n            self.editor_alpha *= delta;\n            self.dirty_canvas = true;\n            self.dirty_bgcanvas = true;\n\n            if (delta < 1 && self.editor_alpha < 0.01) {\n                clearInterval(t);\n                if (delta < 1) {\n                    self.live_mode = true;\n                }\n            }\n            if (delta > 1 && self.editor_alpha > 0.99) {\n                clearInterval(t);\n                self.editor_alpha = 1;\n            }\n        }, 1);\n    };\n\n    LGraphCanvas.prototype.onNodeSelectionChange = function(node) {\n        return; //disabled\n    };\n\n    /* this is an implementation for touch not in production and not ready\n     */\n    /*LGraphCanvas.prototype.touchHandler = function(event) {\n        //alert(\"foo\");\n        var touches = event.changedTouches,\n            first = touches[0],\n            type = \"\";\n\n        switch (event.type) {\n            case \"touchstart\":\n                type = \"mousedown\";\n                break;\n            case \"touchmove\":\n                type = \"mousemove\";\n                break;\n            case \"touchend\":\n                type = \"mouseup\";\n                break;\n            default:\n                return;\n        }\n\n        //initMouseEvent(type, canBubble, cancelable, view, clickCount,\n        //           screenX, screenY, clientX, clientY, ctrlKey,\n        //           altKey, shiftKey, metaKey, button, relatedTarget);\n\n        // this is eventually a Dom object, get the LGraphCanvas back\n        if(typeof this.getCanvasWindow == \"undefined\"){\n            var window = this.lgraphcanvas.getCanvasWindow();\n        }else{\n            var window = this.getCanvasWindow();\n        }\n        \n        var document = window.document;\n\n        var simulatedEvent = document.createEvent(\"MouseEvent\");\n        simulatedEvent.initMouseEvent(\n            type,\n            true,\n            true,\n            window,\n            1,\n            first.screenX,\n            first.screenY,\n            first.clientX,\n            first.clientY,\n            false,\n            false,\n            false,\n            false,\n            0, //left\n            null\n        );\n        first.target.dispatchEvent(simulatedEvent);\n        event.preventDefault();\n    };*/\n\n    /* CONTEXT MENU ********************/\n\n    LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) {\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var group = new LiteGraph.LGraphGroup();\n        group.pos = canvas.convertEventToCanvasOffset(mouse_event);\n        canvas.graph.add(group);\n    };\n\n    /**\n     * Determines the furthest nodes in each direction\n     * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted\n     * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}\n     */\n    LGraphCanvas.getBoundaryNodes = function(nodes) {\n        let top = null;\n        let right = null;\n        let bottom = null;\n        let left = null;\n        for (const nID in nodes) {\n            const node = nodes[nID];\n            const [x, y] = node.pos;\n            const [width, height] = node.size;\n\n            if (top === null || y < top.pos[1]) {\n                top = node;\n            }\n            if (right === null || x + width > right.pos[0] + right.size[0]) {\n                right = node;\n            }\n            if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) {\n                bottom = node;\n            }\n            if (left === null || x < left.pos[0]) {\n                left = node;\n            }\n        }\n\n        return {\n            \"top\": top,\n            \"right\": right,\n            \"bottom\": bottom,\n            \"left\": left\n        };\n    }\n    /**\n     * Determines the furthest nodes in each direction for the currently selected nodes\n     * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}\n     */\n    LGraphCanvas.prototype.boundaryNodesForSelection = function() {\n        return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes));\n    }\n\n    /**\n     *\n     * @param {LGraphNode[]} nodes a list of nodes\n     * @param {\"top\"|\"bottom\"|\"left\"|\"right\"} direction Direction to align the nodes\n     * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction)\n     */\n    LGraphCanvas.alignNodes = function (nodes, direction, align_to) {\n        if (!nodes) {\n            return;\n        }\n\n        const canvas = LGraphCanvas.active_canvas;\n        let boundaryNodes = []\n        if (align_to === undefined) {\n            boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes)\n        } else {\n            boundaryNodes = {\n                \"top\": align_to,\n                \"right\": align_to,\n                \"bottom\": align_to,\n                \"left\": align_to\n            }\n        }\n\n        for (const [_, node] of Object.entries(canvas.selected_nodes)) {\n            switch (direction) {\n                case \"right\":\n                    node.pos[0] = boundaryNodes[\"right\"].pos[0] + boundaryNodes[\"right\"].size[0] - node.size[0];\n                    break;\n                case \"left\":\n                    node.pos[0] = boundaryNodes[\"left\"].pos[0];\n                    break;\n                case \"top\":\n                    node.pos[1] = boundaryNodes[\"top\"].pos[1];\n                    break;\n                case \"bottom\":\n                    node.pos[1] = boundaryNodes[\"bottom\"].pos[1] + boundaryNodes[\"bottom\"].size[1] - node.size[1];\n                    break;\n            }\n        }\n\n        canvas.dirty_canvas = true;\n        canvas.dirty_bgcanvas = true;\n    };\n\n    LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) {\n        new LiteGraph.ContextMenu([\"Top\", \"Bottom\", \"Left\", \"Right\"], {\n            event: event,\n            callback: inner_clicked,\n            parentMenu: prev_menu,\n        });\n\n        function inner_clicked(value) {\n            LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node);\n        }\n    }\n\n    LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) {\n        new LiteGraph.ContextMenu([\"Top\", \"Bottom\", \"Left\", \"Right\"], {\n            event: event,\n            callback: inner_clicked,\n            parentMenu: prev_menu,\n        });\n\n        function inner_clicked(value) {\n            LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase());\n        }\n    }\n\n    LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) {\n\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n        var graph = canvas.graph;\n        if (!graph)\n            return;\n\n        function inner_onMenuAdded(base_category ,prev_menu){\n    \n            var categories  = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function(category){return category.startsWith(base_category)});\n            var entries = [];\n    \n            categories.map(function(category){\n    \n                if (!category) \n                    return;\n    \n                var base_category_regex = new RegExp('^(' + base_category + ')');\n                var category_name = category.replace(base_category_regex,\"\").split('/')[0];\n                var category_path = base_category  === '' ? category_name + '/' : base_category + category_name + '/';\n    \n                var name = category_name;\n                if(name.indexOf(\"::\") != -1) //in case it has a namespace like \"shader::math/rand\" it hides the namespace\n                    name = name.split(\"::\")[1];\n                        \n                var index = entries.findIndex(function(entry){return entry.value === category_path});\n                if (index === -1) {\n                    entries.push({ value: category_path, content: name, has_submenu: true, callback : function(value, event, mouseEvent, contextMenu){\n                        inner_onMenuAdded(value.value, contextMenu)\n                    }});\n                }\n                \n            });\n    \n            var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter );\n            nodes.map(function(node){\n    \n                if (node.skip_list)\n                    return;\n    \n                var entry = { value: node.type, content: node.title, has_submenu: false , callback : function(value, event, mouseEvent, contextMenu){\n                    \n                        var first_event = contextMenu.getFirstEvent();\n                        canvas.graph.beforeChange();\n                        var node = LiteGraph.createNode(value.value);\n                        if (node) {\n                            node.pos = canvas.convertEventToCanvasOffset(first_event);\n                            canvas.graph.add(node);\n                        }\n                        if(callback)\n                            callback(node);\n                        canvas.graph.afterChange();\n                    \n                    }\n                }\n    \n                entries.push(entry);\n    \n            });\n    \n            new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window );\n    \n        }\n    \n        inner_onMenuAdded('',prev_menu);\n        return false;\n    \n    };\n\n    LGraphCanvas.onMenuCollapseAll = function() {};\n\n    LGraphCanvas.onMenuNodeEdit = function() {};\n\n    LGraphCanvas.showMenuNodeOptionalInputs = function(\n        v,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var options = node.optional_inputs;\n        if (node.onGetInputs) {\n            options = node.onGetInputs();\n        }\n\n        var entries = [];\n        if (options) {\n            for (var i=0; i < options.length; i++) {\n                var entry = options[i];\n                if (!entry) {\n                    entries.push(null);\n                    continue;\n                }\n                var label = entry[0];\n\t\t\t\tif(!entry[2])\n\t\t\t\t\tentry[2] = {};\n\n                if (entry[2].label) {\n                    label = entry[2].label;\n                }\n\n\t\t\t\tentry[2].removable = true;\n                var data = { content: label, value: entry };\n                if (entry[1] == LiteGraph.ACTION) {\n                    data.className = \"event\";\n                }\n                entries.push(data);\n            }\n        }\n\n        if (node.onMenuNodeInputs) {\n            var retEntries = node.onMenuNodeInputs(entries);\n            if(retEntries) entries = retEntries;\n        }\n\n        if (!entries.length) {\n\t\t\tconsole.log(\"no input entries\");\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, e, prev) {\n            if (!node) {\n                return;\n            }\n\n            if (v.callback) {\n                v.callback.call(that, node, v, e, prev);\n            }\n\n            if (v.value) {\n\t\t\t\tnode.graph.beforeChange();\n                node.addInput(v.value[0], v.value[1], v.value[2]);\n\n                if (node.onNodeInputAdd) { // callback to the node when adding a slot\n                    node.onNodeInputAdd(v.value);\n                }\n                node.setDirtyCanvas(true, true);\n\t\t\t\tnode.graph.afterChange();\n            }\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.showMenuNodeOptionalOutputs = function(\n        v,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var options = node.optional_outputs;\n        if (node.onGetOutputs) {\n            options = node.onGetOutputs();\n        }\n\n        var entries = [];\n        if (options) {\n            for (var i=0; i < options.length; i++) {\n                var entry = options[i];\n                if (!entry) {\n                    //separator?\n                    entries.push(null);\n                    continue;\n                }\n\n                if (\n                    node.flags &&\n                    node.flags.skip_repeated_outputs &&\n                    node.findOutputSlot(entry[0]) != -1\n                ) {\n                    continue;\n                } //skip the ones already on\n                var label = entry[0];\n\t\t\t\tif(!entry[2])\n\t\t\t\t\tentry[2] = {};\n                if (entry[2].label) {\n                    label = entry[2].label;\n                }\n\t\t\t\tentry[2].removable = true;\n                var data = { content: label, value: entry };\n                if (entry[1] == LiteGraph.EVENT) {\n                    data.className = \"event\";\n                }\n                entries.push(data);\n            }\n        }\n\n        if (this.onMenuNodeOutputs) {\n            entries = this.onMenuNodeOutputs(entries);\n        }\n        if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted\n            if (node.findOutputSlot(\"onExecuted\") == -1){\n                entries.push({content: \"On Executed\", value: [\"onExecuted\", LiteGraph.EVENT, {nameLocked: true}], className: \"event\"}); //, opts: {}\n            }\n        }\n        // add callback for modifing the menu elements onMenuNodeOutputs\n        if (node.onMenuNodeOutputs) {\n            var retEntries = node.onMenuNodeOutputs(entries);\n            if(retEntries) entries = retEntries;\n        }\n\n        if (!entries.length) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, e, prev) {\n            if (!node) {\n                return;\n            }\n\n            if (v.callback) {\n                v.callback.call(that, node, v, e, prev);\n            }\n\n            if (!v.value) {\n                return;\n            }\n\n            var value = v.value[1];\n\n            if (\n                value &&\n                (value.constructor === Object || value.constructor === Array)\n            ) {\n                //submenu why?\n                var entries = [];\n                for (var i in value) {\n                    entries.push({ content: i, value: value[i] });\n                }\n                new LiteGraph.ContextMenu(entries, {\n                    event: e,\n                    callback: inner_clicked,\n                    parentMenu: prev_menu,\n                    node: node\n                });\n                return false;\n            } else {\n\t\t\t\tnode.graph.beforeChange();\n                node.addOutput(v.value[0], v.value[1], v.value[2]);\n\n                if (node.onNodeOutputAdd) { // a callback to the node when adding a slot\n                    node.onNodeOutputAdd(v.value);\n                }\n                node.setDirtyCanvas(true, true);\n\t\t\t\tnode.graph.afterChange();\n            }\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onShowMenuNodeProperties = function(\n        value,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node || !node.properties) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var entries = [];\n        for (var i in node.properties) {\n            var value = node.properties[i] !== undefined ? node.properties[i] : \" \";\n\t\t\tif( typeof value == \"object\" )\n\t\t\t\tvalue = JSON.stringify(value);\n\t\t\tvar info = node.getPropertyInfo(i);\n\t\t\tif(info.type == \"enum\" || info.type == \"combo\")\n\t\t\t\tvalue = LGraphCanvas.getPropertyPrintableValue( value, info.values );\n\n            //value could contain invalid html characters, clean that\n            value = LGraphCanvas.decodeHTML(value);\n            entries.push({\n                content:\n                    \"<span class='property_name'>\" +\n                    (info.label ? info.label : i) +\n                    \"</span>\" +\n                    \"<span class='property_value'>\" +\n                    value +\n                    \"</span>\",\n                value: i\n            });\n        }\n        if (!entries.length) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                allow_html: true,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, options, e, prev) {\n            if (!node) {\n                return;\n            }\n            var rect = this.getBoundingClientRect();\n            canvas.showEditPropertyValue(node, v.value, {\n                position: [rect.left, rect.top]\n            });\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.decodeHTML = function(str) {\n        var e = document.createElement(\"div\");\n        e.innerText = str;\n        return e.innerHTML;\n    };\n\n    LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) {\n        if (!node) {\n            return;\n        }\n        \n\t\tvar fApplyMultiNode = function(node){\n\t\t\tnode.size = node.computeSize();\n\t\t\tif (node.onResize)\n\t\t\t\tnode.onResize(node.size);\n\t\t}\n\t\t\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.prototype.showLinkMenu = function(link, e) {\n        var that = this;\n\t\t// console.log(link);\n\t\tvar node_left = that.graph.getNodeById( link.origin_id );\n\t\tvar node_right = that.graph.getNodeById( link.target_id );\n\t\tvar fromType = false;\n\t\tif (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type;\n        var destType = false;\n\t\tif (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type;\n\t\t\n\t\tvar options = [\"Add Node\",null,\"Delete\",null];\n\t\t\n\t\t\n        var menu = new LiteGraph.ContextMenu(options, {\n            event: e,\n\t\t\ttitle: link.data != null ? link.data.constructor.name : null,\n            callback: inner_clicked\n        });\n\n        function inner_clicked(v,options,e) {\n            switch (v) {\n                case \"Add Node\":\n\t\t\t\t\tLGraphCanvas.onMenuAdd(null, null, e, menu, function(node){\n\t\t\t\t\t\t// console.debug(\"node autoconnect\");\n\t\t\t\t\t\tif(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length){\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// leave the connection type checking inside connectByType\n\t\t\t\t\t\tif (node_left.connectByType( link.origin_slot, node, fromType )){\n                        \tnode.connectByType( link.target_slot, node_right, destType );\n                            node.pos[0] -= node.size[0] * 0.5;\n                        }\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t\t\n                case \"Delete\":\n                    that.graph.removeLink(link.id);\n                    break;\n                default:\n\t\t\t\t\t/*var nodeCreated = createDefaultNodeForSlot({   nodeFrom: node_left\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotFrom: link.origin_slot\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeTo: node\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotTo: link.target_slot\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,e: e\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: \"AUTO\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\tif(nodeCreated) console.log(\"new node in beetween \"+v+\" created\");*/\n            }\n        }\n\n        return false;\n    };\n    \n \tLGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection\n        var optPass = optPass || {};\n        var opts = Object.assign({   nodeFrom: null // input\n                                    ,slotFrom: null // input\n                                    ,nodeTo: null   // output\n                                    ,slotTo: null   // output\n                                    ,position: []\t// pass the event coords\n\t\t\t\t\t\t\t\t  \t,nodeType: null\t// choose a nodetype to add, AUTO to set at first good\n\t\t\t\t\t\t\t\t  \t,posAdd:[0,0]\t// adjust x,y\n\t\t\t\t\t\t\t\t  \t,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h\n                                }\n                                ,optPass\n                            );\n        var that = this;\n        \n        var isFrom = opts.nodeFrom && opts.slotFrom!==null;\n        var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null;\n\t\n        if (!isFrom && !isTo){\n            console.warn(\"No data passed to createDefaultNodeForSlot \"+opts.nodeFrom+\" \"+opts.slotFrom+\" \"+opts.nodeTo+\" \"+opts.slotTo);\n            return false;\n        }\n\t\tif (!opts.nodeType){\n            console.warn(\"No type to createDefaultNodeForSlot\");\n            return false;\n        }\n        \n        var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;\n        var slotX = isFrom ? opts.slotFrom : opts.slotTo;\n        \n        var iSlotConn = false;\n        switch (typeof slotX){\n            case \"string\":\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            case \"object\":\n                // ok slotX\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);\n            break;\n            case \"number\":\n                iSlotConn = slotX;\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n\t\t\tcase \"undefined\":\n            default:\n                // bad ?\n                //iSlotConn = 0;\n                console.warn(\"Cant get slot information \"+slotX);\n                return false;\n        }\n\t\n\t\tif (slotX===false || iSlotConn===false){\n\t\t\tconsole.warn(\"createDefaultNodeForSlot bad slotX \"+slotX+\" \"+iSlotConn);\n\t\t}\n\t\t\n\t\t// check for defaults nodes for this slottype\n\t\tvar fromSlotType = slotX.type==LiteGraph.EVENT?\"_event_\":slotX.type;\n\t\tvar slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;\n\t\tif(slotTypesDefault && slotTypesDefault[fromSlotType]){\n\t\t\tif (slotX.link !== null) {\n\t\t\t\t// is connected\n\t\t\t}else{\n\t\t\t\t// is not not connected\n\t\t\t}\n\t\t\tnodeNewType = false;\n\t\t\tif(typeof slotTypesDefault[fromSlotType] == \"object\" || typeof slotTypesDefault[fromSlotType] == \"array\"){\n\t\t\t\tfor(var typeX in slotTypesDefault[fromSlotType]){\n\t\t\t\t\tif (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == \"AUTO\"){\n\t\t\t\t\t\tnodeNewType = slotTypesDefault[fromSlotType][typeX];\n\t\t\t\t\t\t// console.log(\"opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: \"+opts.nodeType);\n\t\t\t\t\t\tbreak; // --------\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\tif (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == \"AUTO\") nodeNewType = slotTypesDefault[fromSlotType];\n\t\t\t}\n\t\t\tif (nodeNewType) {\n\t\t\t\tvar nodeNewOpts = false;\n\t\t\t\tif (typeof nodeNewType == \"object\" && nodeNewType.node){\n\t\t\t\t\tnodeNewOpts = nodeNewType;\n\t\t\t\t\tnodeNewType = nodeNewType.node;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t//that.graph.beforeChange();\n\t\t\t\t\n\t\t\t\tvar newNode = LiteGraph.createNode(nodeNewType);\n\t\t\t\tif(newNode){\n\t\t\t\t\t// if is object pass options\n\t\t\t\t\tif (nodeNewOpts){\n\t\t\t\t\t\tif (nodeNewOpts.properties) {\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.properties) {\n\t\t\t\t\t\t\t\tnewNode.addProperty( i, nodeNewOpts.properties[i] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.inputs) {\n\t\t\t\t\t\t\tnewNode.inputs = [];\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.inputs) {\n\t\t\t\t\t\t\t\tnewNode.addOutput(\n\t\t\t\t\t\t\t\t\tnodeNewOpts.inputs[i][0],\n\t\t\t\t\t\t\t\t\tnodeNewOpts.inputs[i][1]\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.outputs) {\n\t\t\t\t\t\t\tnewNode.outputs = [];\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.outputs) {\n\t\t\t\t\t\t\t\tnewNode.addOutput(\n\t\t\t\t\t\t\t\t\tnodeNewOpts.outputs[i][0],\n\t\t\t\t\t\t\t\t\tnodeNewOpts.outputs[i][1]\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.title) {\n\t\t\t\t\t\t\tnewNode.title = nodeNewOpts.title;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.json) {\n\t\t\t\t\t\t\tnewNode.configure(nodeNewOpts.json);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// add the node\n\t\t\t\t\tthat.graph.add(newNode);\n\t\t\t\t\tnewNode.pos = [\topts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0)\n\t\t\t\t\t\t\t\t   \t,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/\n\t\t\t\t\t\n\t\t\t\t\t//that.graph.afterChange();\n\t\t\t\t\t\n\t\t\t\t\t// connect the two!\n\t\t\t\t\tif (isFrom){\n\t\t\t\t\t\topts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType );\n\t\t\t\t\t}else{\n\t\t\t\t\t\topts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType );\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// if connecting in between\n\t\t\t\t\tif (isFrom && isTo){\n\t\t\t\t\t\t// TODO\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treturn true;\n\t\t\t\t\t\n\t\t\t\t}else{\n\t\t\t\t\tconsole.log(\"failed creating \"+nodeNewType);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n \n    LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection\n        var optPass = optPass || {};\n        var opts = Object.assign({   nodeFrom: null  // input\n                                    ,slotFrom: null // input\n                                    ,nodeTo: null   // output\n                                    ,slotTo: null   // output\n                                    ,e: null\n                                }\n                                ,optPass\n                            );\n        var that = this;\n        \n        var isFrom = opts.nodeFrom && opts.slotFrom;\n        var isTo = !isFrom && opts.nodeTo && opts.slotTo;\n        \n        if (!isFrom && !isTo){\n            console.warn(\"No data passed to showConnectionMenu\");\n            return false;\n        }\n        \n        var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;\n        var slotX = isFrom ? opts.slotFrom : opts.slotTo;\n        \n        var iSlotConn = false;\n        switch (typeof slotX){\n            case \"string\":\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            case \"object\":\n                // ok slotX\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);\n            break;\n            case \"number\":\n                iSlotConn = slotX;\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            default:\n                // bad ?\n                //iSlotConn = 0;\n                console.warn(\"Cant get slot information \"+slotX);\n                return false;\n        }\n            \n\t\tvar options = [\"Add Node\",null];\n\t\t\n\t\tif (that.allow_searchbox){\n\t\t\toptions.push(\"Search\");\n\t\t\toptions.push(null);\n\t\t}\n\t\t\n\t\t// get defaults nodes for this slottype\n\t\tvar fromSlotType = slotX.type==LiteGraph.EVENT?\"_event_\":slotX.type;\n\t\tvar slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;\n\t\tif(slotTypesDefault && slotTypesDefault[fromSlotType]){\n\t\t\tif(typeof slotTypesDefault[fromSlotType] == \"object\" || typeof slotTypesDefault[fromSlotType] == \"array\"){\n\t\t\t\tfor(var typeX in slotTypesDefault[fromSlotType]){\n\t\t\t\t\toptions.push(slotTypesDefault[fromSlotType][typeX]);\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\toptions.push(slotTypesDefault[fromSlotType]);\n\t\t\t}\n\t\t}\n\t\t\n\t\t// build menu\n        var menu = new LiteGraph.ContextMenu(options, {\n            event: opts.e,\n\t\t\ttitle: (slotX && slotX.name!=\"\" ? (slotX.name + (fromSlotType?\" | \":\"\")) : \"\")+(slotX && fromSlotType ? fromSlotType : \"\"),\n            callback: inner_clicked\n        });\n        \n\t\t// callback\n        function inner_clicked(v,options,e) {\n            //console.log(\"Process showConnectionMenu selection\");\n            switch (v) {\n                case \"Add Node\":\n                    LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){\n                        if (isFrom){\n                            opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType );\n                        }else{\n                            opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType );\n                        }\n                    });\n                    break;\n\t\t\t\tcase \"Search\":\n\t\t\t\t\tif(isFrom){\n\t\t\t\t\t\tthat.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType});\n\t\t\t\t\t}else{\n\t\t\t\t\t\tthat.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType});\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n                default:\n\t\t\t\t\t// check for defaults nodes for this slottype\n\t\t\t\t\tvar nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: v\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\tif (nodeCreated){\n\t\t\t\t\t\t// new node created\n\t\t\t\t\t\t//console.log(\"node \"+v+\" created\")\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// failed or v is not in defaults\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n            }\n        }   \n        \n        return false;\n    };\n\n    // TODO refactor :: this is used fot title but not for properties!\n    LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) {\n        var input_html = \"\";\n        var property = item.property || \"title\";\n        var value = node[property];\n\n        // TODO refactor :: use createDialog ?\n        \n        var dialog = document.createElement(\"div\");\n        dialog.is_modified = false;\n        dialog.className = \"graphdialog\";\n        dialog.innerHTML =\n            \"<span class='name'></span><input autofocus type='text' class='value'/><button>OK</button>\";\n        dialog.close = function() {\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n        var title = dialog.querySelector(\".name\");\n        title.innerText = property;\n        var input = dialog.querySelector(\".value\");\n        if (input) {\n            input.value = value;\n            input.addEventListener(\"blur\", function(e) {\n                this.focus();\n            });\n            input.addEventListener(\"keydown\", function(e) {\n                dialog.is_modified = true;\n                if (e.keyCode == 27) {\n                    //ESC\n                    dialog.close();\n                } else if (e.keyCode == 13) {\n                    inner(); // save\n                } else if (e.keyCode != 13 && e.target.localName != \"textarea\") {\n                    return;\n                }\n                e.preventDefault();\n                e.stopPropagation();\n            });\n        }\n\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n\n        var rect = canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", inner);\n        canvas.parentNode.appendChild(dialog);\n\n        if(input) input.focus();\n        \n        var dialogCloseTimer = null;\n        dialog.addEventListener(\"mouseleave\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        dialog.addEventListener(\"mouseenter\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        \n        function inner() {\n            if(input) setValue(input.value);\n        }\n\n        function setValue(value) {\n            if (item.type == \"Number\") {\n                value = Number(value);\n            } else if (item.type == \"Boolean\") {\n                value = Boolean(value);\n            }\n            node[property] = value;\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n            node.setDirtyCanvas(true, true);\n        }\n    };\n\n    // refactor: there are different dialogs, some uses createDialog some dont\n    LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) {\n        var that = this;\n        var input_html = \"\";\n        title = title || \"\";\n\n        var dialog = document.createElement(\"div\");\n        dialog.is_modified = false;\n        dialog.className = \"graphdialog rounded\";\n        if(multiline)\n\t        dialog.innerHTML = \"<span class='name'></span> <textarea autofocus class='value'></textarea><button class='rounded'>OK</button>\";\n\t\telse\n        \tdialog.innerHTML = \"<span class='name'></span> <input autofocus type='text' class='value'/><button class='rounded'>OK</button>\";\n        dialog.close = function() {\n            that.prompt_box = null;\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n        canvas.parentNode.appendChild(dialog);\n        \n        if (this.ds.scale > 1) {\n            dialog.style.transform = \"scale(\" + this.ds.scale + \")\";\n        }\n\n        var dialogCloseTimer = null;\n        var prevent_timeout = false;\n        LiteGraph.pointerListenerAdd(dialog,\"leave\", function(e) {\n            if (prevent_timeout)\n                return;\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        LiteGraph.pointerListenerAdd(dialog,\"enter\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        var selInDia = dialog.querySelectorAll(\"select\");\n        if (selInDia){\n            // if filtering, check focus changed to comboboxes and prevent closing\n            selInDia.forEach(function(selIn) {\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            });\n        }\n\n        if (that.prompt_box) {\n            that.prompt_box.close();\n        }\n        that.prompt_box = dialog;\n\n        var first = null;\n        var timeout = null;\n        var selected = null;\n\n        var name_element = dialog.querySelector(\".name\");\n        name_element.innerText = title;\n        var value_element = dialog.querySelector(\".value\");\n        value_element.value = value;\n\n        var input = value_element;\n        input.addEventListener(\"keydown\", function(e) {\n            dialog.is_modified = true;\n            if (e.keyCode == 27) {\n                //ESC\n                dialog.close();\n            } else if (e.keyCode == 13 && e.target.localName != \"textarea\") {\n                if (callback) {\n                    callback(this.value);\n                }\n                dialog.close();\n            } else {\n                return;\n            }\n            e.preventDefault();\n            e.stopPropagation();\n        });\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", function(e) {\n            if (callback) {\n                callback(input.value);\n            }\n            that.setDirty(true);\n            dialog.close();\n        });\n\n        var rect = canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n\n        setTimeout(function() {\n            input.focus();\n        }, 10);\n\n        return dialog;\n    };\n\n    LGraphCanvas.search_limit = -1;\n    LGraphCanvas.prototype.showSearchBox = function(event, options) {\n        // proposed defaults\n        var def_options = { slot_from: null\n                        ,node_from: null\n                        ,node_to: null\n                        ,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out\n                        ,type_filter_in: false                          // these are default: pass to set initially set values\n                        ,type_filter_out: false\n                        ,show_general_if_none_on_typefilter: true\n                        ,show_general_after_typefiltered: true\n                        ,hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave\n                        ,show_all_if_empty: true\n                        ,show_all_on_open: LiteGraph.search_show_all_on_open\n                    };\n        options = Object.assign(def_options, options || {});\n        \n\t\t//console.log(options);\n\t\t\n        var that = this;\n        var input_html = \"\";\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n        var root_document = canvas.ownerDocument || document;\n\n        var dialog = document.createElement(\"div\");\n        dialog.className = \"litegraph litesearchbox graphdialog rounded\";\n        dialog.innerHTML = \"<span class='name'>Search</span> <input autofocus type='text' class='value rounded'/>\";\n        if (options.do_type_filter){\n            dialog.innerHTML += \"<select class='slot_in_type_filter'><option value=''></option></select>\";\n            dialog.innerHTML += \"<select class='slot_out_type_filter'><option value=''></option></select>\";\n        }\n        dialog.innerHTML += \"<div class='helper'></div>\";\n        \n        if( root_document.fullscreenElement )\n\t        root_document.fullscreenElement.appendChild(dialog);\n\t\telse\n\t\t{\n\t\t    root_document.body.appendChild(dialog);\n\t\t\troot_document.body.style.overflow = \"hidden\";\n\t\t}\n        // dialog element has been appended\n        \n        if (options.do_type_filter){\n            var selIn = dialog.querySelector(\".slot_in_type_filter\");\n            var selOut = dialog.querySelector(\".slot_out_type_filter\");\n        }\n        \n        dialog.close = function() {\n            that.search_box = null;\n\t\t\tthis.blur();\n            canvas.focus();\n\t\t\troot_document.body.style.overflow = \"\";\n\n            setTimeout(function() {\n                that.canvas.focus();\n            }, 20); //important, if canvas loses focus keys wont be captured\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n\n        if (this.ds.scale > 1) {\n            dialog.style.transform = \"scale(\" + this.ds.scale + \")\";\n        }\n\n        // hide on mouse leave\n        if(options.hide_on_mouse_leave){\n            var prevent_timeout = false;\n            var timeout_close = null;\n            LiteGraph.pointerListenerAdd(dialog,\"enter\", function(e) {\n                if (timeout_close) {\n                    clearTimeout(timeout_close);\n                    timeout_close = null;\n                }\n            });\n            LiteGraph.pointerListenerAdd(dialog,\"leave\", function(e) {\n                if (prevent_timeout){\n                    return;\n                }\n                timeout_close = setTimeout(function() {\n                    dialog.close();\n                }, 500);\n            });\n            // if filtering, check focus changed to comboboxes and prevent closing\n            if (options.do_type_filter){\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n                selOut.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selOut.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selOut.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            }\n        }\n\n        if (that.search_box) {\n            that.search_box.close();\n        }\n        that.search_box = dialog;\n\n        var helper = dialog.querySelector(\".helper\");\n\n        var first = null;\n        var timeout = null;\n        var selected = null;\n\n        var input = dialog.querySelector(\"input\");\n        if (input) {\n            input.addEventListener(\"blur\", function(e) {\n                if(that.search_box)\n                    this.focus();\n            });\n            input.addEventListener(\"keydown\", function(e) {\n                if (e.keyCode == 38) {\n                    //UP\n                    changeSelection(false);\n                } else if (e.keyCode == 40) {\n                    //DOWN\n                    changeSelection(true);\n                } else if (e.keyCode == 27) {\n                    //ESC\n                    dialog.close();\n                } else if (e.keyCode == 13) {\n                    refreshHelper();\n                    if (selected) {\n                        select(selected.innerHTML);\n                    } else if (first) {\n                        select(first);\n                    } else {\n                        dialog.close();\n                    }\n                } else {\n                    if (timeout) {\n                        clearInterval(timeout);\n                    }\n                    timeout = setTimeout(refreshHelper, 250);\n                    return;\n                }\n                e.preventDefault();\n                e.stopPropagation();\n\t\t\t\te.stopImmediatePropagation();\n\t\t\t\treturn true;\n            });\n        }\n        \n        // if should filter on type, load and fill selected and choose elements if passed\n        if (options.do_type_filter){\n            if (selIn){\n                var aSlots = LiteGraph.slot_types_in;\n                var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length;\n                \n                if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION)\n                    options.type_filter_in = \"_event_\";\n                /* this will filter on * .. but better do it manually in case\n                else if(options.type_filter_in === \"\" || options.type_filter_in === 0)\n                    options.type_filter_in = \"*\";*/\n                \n                for (var iK=0; iK<nSlots; iK++){\n                    var opt = document.createElement('option');\n                    opt.value = aSlots[iK];\n                    opt.innerHTML = aSlots[iK];\n                    selIn.appendChild(opt);\n                    if(options.type_filter_in !==false && (options.type_filter_in+\"\").toLowerCase() == (aSlots[iK]+\"\").toLowerCase()){\n                        //selIn.selectedIndex ..\n                        opt.selected = true;\n\t\t\t\t\t\t//console.log(\"comparing IN \"+options.type_filter_in+\" :: \"+aSlots[iK]);\n\t                }else{\n\t\t\t\t\t\t//console.log(\"comparing OUT \"+options.type_filter_in+\" :: \"+aSlots[iK]);\n\t\t\t\t\t}\n\t\t\t\t}\n                selIn.addEventListener(\"change\",function(){\n                    refreshHelper();\n                });\n            }\n            if (selOut){\n                var aSlots = LiteGraph.slot_types_out;\n                var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; \n                \n                if (options.type_filter_out == LiteGraph.EVENT || options.type_filter_out == LiteGraph.ACTION)\n                    options.type_filter_out = \"_event_\";\n                /* this will filter on * .. but better do it manually in case\n                else if(options.type_filter_out === \"\" || options.type_filter_out === 0)\n                    options.type_filter_out = \"*\";*/\n                \n                for (var iK=0; iK<nSlots; iK++){\n                    var opt = document.createElement('option');\n                    opt.value = aSlots[iK];\n                    opt.innerHTML = aSlots[iK];\n                    selOut.appendChild(opt);\n                    if(options.type_filter_out !==false && (options.type_filter_out+\"\").toLowerCase() == (aSlots[iK]+\"\").toLowerCase()){\n                        //selOut.selectedIndex ..\n                        opt.selected = true;\n                    }\n                }\n                selOut.addEventListener(\"change\",function(){\n                    refreshHelper();\n                });\n            }\n        }\n        \n        //compute best position\n        var rect = canvas.getBoundingClientRect();\n\n        var left = ( event ? event.clientX : (rect.left + rect.width * 0.5) ) - 80;\n        var top = ( event ? event.clientY : (rect.top + rect.height * 0.5) ) - 20;\n        dialog.style.left = left + \"px\";\n        dialog.style.top = top + \"px\";\n\n\t\t//To avoid out of screen problems\n\t\tif(event.layerY > (rect.height - 200)) \n            helper.style.maxHeight = (rect.height - event.layerY - 20) + \"px\";\n\n\t\t/*\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n        canvas.parentNode.appendChild(dialog);\n\t\t*/\n\n        input.focus();\n        if (options.show_all_on_open) refreshHelper();\n\n        function select(name) {\n            if (name) {\n                if (that.onSearchBoxSelection) {\n                    that.onSearchBoxSelection(name, event, graphcanvas);\n                } else {\n                    var extra = LiteGraph.searchbox_extras[name.toLowerCase()];\n                    if (extra) {\n                        name = extra.type;\n                    }\n\n\t\t\t\t\tgraphcanvas.graph.beforeChange();\n                    var node = LiteGraph.createNode(name);\n                    if (node) {\n                        node.pos = graphcanvas.convertEventToCanvasOffset(\n                            event\n                        );\n                        graphcanvas.graph.add(node, false);\n                    }\n\n                    if (extra && extra.data) {\n                        if (extra.data.properties) {\n                            for (var i in extra.data.properties) {\n                                node.addProperty( i, extra.data.properties[i] );\n                            }\n                        }\n                        if (extra.data.inputs) {\n                            node.inputs = [];\n                            for (var i in extra.data.inputs) {\n                                node.addOutput(\n                                    extra.data.inputs[i][0],\n                                    extra.data.inputs[i][1]\n                                );\n                            }\n                        }\n                        if (extra.data.outputs) {\n                            node.outputs = [];\n                            for (var i in extra.data.outputs) {\n                                node.addOutput(\n                                    extra.data.outputs[i][0],\n                                    extra.data.outputs[i][1]\n                                );\n                            }\n                        }\n                        if (extra.data.title) {\n                            node.title = extra.data.title;\n                        }\n                        if (extra.data.json) {\n                            node.configure(extra.data.json);\n                        }\n\n                    }\n\n                    // join node after inserting\n                    if (options.node_from){\n                        var iS = false;\n                        switch (typeof options.slot_from){\n                            case \"string\":\n                                iS = options.node_from.findOutputSlot(options.slot_from);    \n                            break;\n                            case \"object\":\n                                if (options.slot_from.name){\n                                    iS = options.node_from.findOutputSlot(options.slot_from.name);\n                                }else{\n                                    iS = -1;\n                                }\n                                if (iS==-1 && typeof options.slot_from.slot_index !== \"undefined\") iS = options.slot_from.slot_index;\n                            break;\n                            case \"number\":\n                                iS = options.slot_from;\n                            break;\n                            default:\n                                iS = 0; // try with first if no name set\n                        }\n                        if (typeof options.node_from.outputs[iS] !== \"undefined\"){\n                            if (iS!==false && iS>-1){\n                                options.node_from.connectByType( iS, node, options.node_from.outputs[iS].type );\n                            }\n                        }else{\n                            // console.warn(\"cant find slot \" + options.slot_from);\n                        }\n                    }\n                    if (options.node_to){\n                        var iS = false;\n                        switch (typeof options.slot_from){\n                            case \"string\":\n                                iS = options.node_to.findInputSlot(options.slot_from);    \n                            break;\n                            case \"object\":\n                                if (options.slot_from.name){\n                                    iS = options.node_to.findInputSlot(options.slot_from.name);\n                                }else{\n                                    iS = -1;\n                                }\n                                if (iS==-1 && typeof options.slot_from.slot_index !== \"undefined\") iS = options.slot_from.slot_index;\n                            break;\n                            case \"number\":\n                                iS = options.slot_from;\n                            break;\n                            default:\n                                iS = 0; // try with first if no name set\n                        }\n                        if (typeof options.node_to.inputs[iS] !== \"undefined\"){\n                            if (iS!==false && iS>-1){\n                                // try connection\n                                options.node_to.connectByTypeOutput(iS,node,options.node_to.inputs[iS].type);\n                            }\n                        }else{\n                            // console.warn(\"cant find slot_nodeTO \" + options.slot_from);\n                        }\n                    }\n                    \n                    graphcanvas.graph.afterChange();\n                }\n            }\n\n            dialog.close();\n        }\n\n        function changeSelection(forward) {\n            var prev = selected;\n            if (selected) {\n                selected.classList.remove(\"selected\");\n            }\n            if (!selected) {\n                selected = forward\n                    ? helper.childNodes[0]\n                    : helper.childNodes[helper.childNodes.length];\n            } else {\n                selected = forward\n                    ? selected.nextSibling\n                    : selected.previousSibling;\n                if (!selected) {\n                    selected = prev;\n                }\n            }\n            if (!selected) {\n                return;\n            }\n            selected.classList.add(\"selected\");\n            selected.scrollIntoView({block: \"end\", behavior: \"smooth\"});\n        }\n\n        function refreshHelper() {\n            timeout = null;\n            var str = input.value;\n            first = null;\n            helper.innerHTML = \"\";\n            if (!str && !options.show_all_if_empty) {\n                return;\n            }\n\n            if (that.onSearchBox) {\n                var list = that.onSearchBox(helper, str, graphcanvas);\n                if (list) {\n                    for (var i = 0; i < list.length; ++i) {\n                        addResult(list[i]);\n                    }\n                }\n            } else {\n                var c = 0;\n                str = str.toLowerCase();\n\t\t\t\tvar filter = graphcanvas.filter || graphcanvas.graph.filter;\n\n                // filter by type preprocess\n                if(options.do_type_filter && that.search_box){\n                    var sIn = that.search_box.querySelector(\".slot_in_type_filter\");\n                    var sOut = that.search_box.querySelector(\".slot_out_type_filter\");\n                }else{\n                    var sIn = false;\n                    var sOut = false;\n                }\n                \n                //extras\n                for (var i in LiteGraph.searchbox_extras) {\n                    var extra = LiteGraph.searchbox_extras[i];\n                    if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) {\n                        continue;\n                    }\n\t\t\t\t\tvar ctor = LiteGraph.registered_node_types[ extra.type ];\n\t\t\t\t\tif( ctor && ctor.filter != filter )\n\t\t\t\t\t\tcontinue;\n                    if( ! inner_test_filter(extra.type) )\n                        continue;\n                    addResult( extra.desc, \"searchbox_extra\" );\n                    if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                        break;\n                    }\n                }\n\n\t\t\t\tvar filtered = null;\n                if (Array.prototype.filter) { //filter supported\n                    var keys = Object.keys( LiteGraph.registered_node_types ); //types\n                    var filtered = keys.filter( inner_test_filter );\n                } else {\n\t\t\t\t\tfiltered = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i) )\n\t\t\t\t\t\t\tfiltered.push(i);\n                    }\n                }\n\n\t\t\t\tfor (var i = 0; i < filtered.length; i++) {\n\t\t\t\t\taddResult(filtered[i]);\n\t\t\t\t\tif ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n                \n                // add general type if filtering\n                if (options.show_general_after_typefiltered\n                    && (sIn.value || sOut.value) \n                ){\n                    filtered_extra = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?\"*\":false, outTypeOverride: sOut&&sOut.value?\"*\":false}) )\n\t\t\t\t\t\t\tfiltered_extra.push(i);\n                    }\n                    for (var i = 0; i < filtered_extra.length; i++) {\n                        addResult(filtered_extra[i], \"generic_type\");\n                        if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                            break;\n                        }\n                    }\n                }\n                \n                // check il filtering gave no results\n                if ((sIn.value || sOut.value) && \n                    ( (helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) )\n                ){\n                    filtered_extra = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i, {skipFilter: true}) )\n\t\t\t\t\t\t\tfiltered_extra.push(i);\n                    }\n                    for (var i = 0; i < filtered_extra.length; i++) {\n                        addResult(filtered_extra[i], \"not_in_filter\");\n                        if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                            break;\n                        }\n                    }\n                }\n                \n\t\t\t\tfunction inner_test_filter( type, optsIn )\n\t\t\t\t{\n                    var optsIn = optsIn || {};\n                    var optsDef = { skipFilter: false\n                                    ,inTypeOverride: false\n                                    ,outTypeOverride: false\n                                  };\n                    var opts = Object.assign(optsDef,optsIn);\n\t\t\t\t\tvar ctor = LiteGraph.registered_node_types[ type ];\n\t\t\t\t\tif(filter && ctor.filter != filter )\n\t\t\t\t\t\treturn false;\n                    if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1)\n                        return false;\n                    \n                    // filter by slot IN, OUT types\n                    if(options.do_type_filter && !opts.skipFilter){\n                        var sType = type;\n                        \n                        var sV = sIn.value;\n                        if (opts.inTypeOverride!==false) sV = opts.inTypeOverride;\n\t\t\t\t\t\t//if (sV.toLowerCase() == \"_event_\") sV = LiteGraph.EVENT; // -1\n                        \n                        if(sIn && sV){\n                            //console.log(\"will check filter against \"+sV);\n                            if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored\n                                //console.debug(\"check \"+sType+\" in \"+LiteGraph.registered_slot_in_types[sV].nodes);\n                                var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType);\n                                if (doesInc!==false){\n                                    //console.log(sType+\" HAS \"+sV);\n                                }else{\n                                    /*console.debug(LiteGraph.registered_slot_in_types[sV]);\n                                    console.log(+\" DONT includes \"+type);*/\n                                    return false;\n                                }\n                            }\n                        }\n                        \n                        var sV = sOut.value;\n                        if (opts.outTypeOverride!==false) sV = opts.outTypeOverride;\n                        //if (sV.toLowerCase() == \"_event_\") sV = LiteGraph.EVENT; // -1\n                        \n                        if(sOut && sV){\n                            //console.log(\"search will check filter against \"+sV);\n                            if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored\n                                //console.debug(\"check \"+sType+\" in \"+LiteGraph.registered_slot_out_types[sV].nodes);\n                                var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType);\n                                if (doesInc!==false){\n                                    //console.log(sType+\" HAS \"+sV);\n                                }else{\n                                    /*console.debug(LiteGraph.registered_slot_out_types[sV]);\n                                    console.log(+\" DONT includes \"+type);*/\n                                    return false;\n                                }\n                            }\n                        }\n                    }\n                    return true;\n\t\t\t\t}\n            }\n\n            function addResult(type, className) {\n                var help = document.createElement(\"div\");\n                if (!first) {\n                    first = type;\n                }\n                help.innerText = type;\n                help.dataset[\"type\"] = escape(type);\n                help.className = \"litegraph lite-search-item\";\n                if (className) {\n                    help.className += \" \" + className;\n                }\n                help.addEventListener(\"click\", function(e) {\n                    select(unescape(this.dataset[\"type\"]));\n                });\n                helper.appendChild(help);\n            }\n        }\n\n        return dialog;\n    };\n\n    LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) {\n        if (!node || node.properties[property] === undefined) {\n            return;\n        }\n\n        options = options || {};\n        var that = this;\n\n        var info = node.getPropertyInfo(property);\n\t\tvar type = info.type;\n\n        var input_html = \"\";\n\n        if (type == \"string\" || type == \"number\" || type == \"array\" || type == \"object\") {\n            input_html = \"<input autofocus type='text' class='value'/>\";\n        } else if ( (type == \"enum\" || type == \"combo\") && info.values) {\n            input_html = \"<select autofocus type='text' class='value'>\";\n            for (var i in info.values) {\n                var v = i;\n\t\t\t\tif( info.values.constructor === Array )\n\t\t\t\t\tv = info.values[i];\n\n                input_html +=\n                    \"<option value='\" +\n                    v +\n                    \"' \" +\n                    (v == node.properties[property] ? \"selected\" : \"\") +\n                    \">\" +\n                    info.values[i] +\n                    \"</option>\";\n            }\n            input_html += \"</select>\";\n        } else if (type == \"boolean\" || type == \"toggle\") {\n            input_html =\n                \"<input autofocus type='checkbox' class='value' \" +\n                (node.properties[property] ? \"checked\" : \"\") +\n                \"/>\";\n        } else {\n            console.warn(\"unknown type: \" + type);\n            return;\n        }\n\n        var dialog = this.createDialog(\n            \"<span class='name'>\" +\n                (info.label ? info.label : property) +\n                \"</span>\" +\n                input_html +\n                \"<button>OK</button>\",\n            options\n        );\n\n        var input = false;\n        if ((type == \"enum\" || type == \"combo\") && info.values) {\n            input = dialog.querySelector(\"select\");\n            input.addEventListener(\"change\", function(e) {\n                dialog.modified();\n                setValue(e.target.value);\n                //var index = e.target.value;\n                //setValue( e.options[e.selectedIndex].value );\n            });\n        } else if (type == \"boolean\" || type == \"toggle\") {\n            input = dialog.querySelector(\"input\");\n            if (input) {\n                input.addEventListener(\"click\", function(e) {\n                    dialog.modified();\n                    setValue(!!input.checked);\n                });\n            }\n        } else {\n            input = dialog.querySelector(\"input\");\n            if (input) {\n                input.addEventListener(\"blur\", function(e) {\n                    this.focus();\n                });\n\n\t\t\t\tvar v = node.properties[property] !== undefined ? node.properties[property] : \"\";\n\t\t\t\tif (type !== 'string') {\n                    v = JSON.stringify(v);\n                }\n\n                input.value = v;\n                input.addEventListener(\"keydown\", function(e) {\n                    if (e.keyCode == 27) {\n                        //ESC\n                        dialog.close();\n                    } else if (e.keyCode == 13) {\n                        // ENTER\n                        inner(); // save\n                    } else if (e.keyCode != 13) {\n                        dialog.modified();\n                        return;\n                    }\n                    e.preventDefault();\n                    e.stopPropagation();\n                });\n            }\n        }\n        if (input) input.focus();\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", inner);\n\n        function inner() {\n            setValue(input.value);\n        }\n\n        function setValue(value) {\n\n\t\t\tif(info && info.values && info.values.constructor === Object && info.values[value] != undefined )\n\t\t\t\tvalue = info.values[value];\n\n            if (typeof node.properties[property] == \"number\") {\n                value = Number(value);\n            }\n            if (type == \"array\" || type == \"object\") {\n                value = JSON.parse(value);\n            }\n            node.properties[property] = value;\n            if (node.graph) {\n                node.graph._version++;\n            }\n            if (node.onPropertyChanged) {\n                node.onPropertyChanged(property, value);\n            }\n\t\t\tif(options.onclose)\n\t\t\t\toptions.onclose();\n            dialog.close();\n            node.setDirtyCanvas(true, true);\n        }\n\n\t\treturn dialog;\n    };\n\n    // TODO refactor, theer are different dialog, some uses createDialog, some dont\n    LGraphCanvas.prototype.createDialog = function(html, options) {\n        var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true };\n        options = Object.assign(def_options, options || {});\n\n        var dialog = document.createElement(\"div\");\n        dialog.className = \"graphdialog\";\n        dialog.innerHTML = html;\n        dialog.is_modified = false;\n\n        var rect = this.canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (options.position) {\n            offsetx += options.position[0];\n            offsety += options.position[1];\n        } else if (options.event) {\n            offsetx += options.event.clientX;\n            offsety += options.event.clientY;\n        } //centered\n        else {\n            offsetx += this.canvas.width * 0.5;\n            offsety += this.canvas.height * 0.5;\n        }\n\n        dialog.style.left = offsetx + \"px\";\n        dialog.style.top = offsety + \"px\";\n\n        this.canvas.parentNode.appendChild(dialog);\n        \n        // acheck for input and use default behaviour: save on enter, close on esc\n        if (options.checkForInput){\n            var aI = [];\n            var focused = false;\n            if (aI = dialog.querySelectorAll(\"input\")){\n                aI.forEach(function(iX) {\n                    iX.addEventListener(\"keydown\",function(e){\n                        dialog.modified();\n                        if (e.keyCode == 27) {\n                            dialog.close();\n                        } else if (e.keyCode != 13) {\n                            return;\n                        }\n                        // set value ?\n                        e.preventDefault();\n                        e.stopPropagation();\n                    });\n                    if (!focused) iX.focus();\n                });\n            }\n        }\n        \n        dialog.modified = function(){\n            dialog.is_modified = true;\n        }\n        dialog.close = function() {\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n        \n        var dialogCloseTimer = null;\n        var prevent_timeout = false;\n        dialog.addEventListener(\"mouseleave\", function(e) {\n            if (prevent_timeout)\n                return;\n            if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        dialog.addEventListener(\"mouseenter\", function(e) {\n            if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        var selInDia = dialog.querySelectorAll(\"select\");\n        if (selInDia){\n            // if filtering, check focus changed to comboboxes and prevent closing\n            selInDia.forEach(function(selIn) {\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            });\n        }\n\n        return dialog;\n    };\n\n\tLGraphCanvas.prototype.createPanel = function(title, options) {\n\t\toptions = options || {};\n\n\t\tvar ref_window = options.window || window;\n\t\tvar root = document.createElement(\"div\");\n\t\troot.className = \"litegraph dialog\";\n\t\troot.innerHTML = \"<div class='dialog-header'><span class='dialog-title'></span></div><div class='dialog-content'></div><div style='display:none;' class='dialog-alt-content'></div><div class='dialog-footer'></div>\";\n\t\troot.header = root.querySelector(\".dialog-header\");\n\n\t\tif(options.width)\n\t\t\troot.style.width = options.width + (options.width.constructor === Number ? \"px\" : \"\");\n\t\tif(options.height)\n\t\t\troot.style.height = options.height + (options.height.constructor === Number ? \"px\" : \"\");\n\t\tif(options.closable)\n\t\t{\n\t\t\tvar close = document.createElement(\"span\");\n\t\t\tclose.innerHTML = \"&#10005;\";\n\t\t\tclose.classList.add(\"close\");\n\t\t\tclose.addEventListener(\"click\",function(){\n\t\t\t\troot.close();\n\t\t\t});\n\t\t\troot.header.appendChild(close);\n\t\t}\n\t\troot.title_element = root.querySelector(\".dialog-title\");\n\t\troot.title_element.innerText = title;\n\t\troot.content = root.querySelector(\".dialog-content\");\n        root.alt_content = root.querySelector(\".dialog-alt-content\");\n\t\troot.footer = root.querySelector(\".dialog-footer\");\n\n\t\troot.close = function()\n\t\t{\n\t\t    if (root.onClose && typeof root.onClose == \"function\"){\n\t\t        root.onClose();\n\t\t    }\n            if(root.parentNode)\n\t\t        root.parentNode.removeChild(root);\n\t\t    /* XXX CHECK THIS */\n\t\t    if(this.parentNode){\n\t\t    \tthis.parentNode.removeChild(this);\n\t\t    }\n\t\t    /* XXX this was not working, was fixed with an IF, check this */\n\t\t}\n\n        // function to swap panel content\n        root.toggleAltContent = function(force){\n            if (typeof force != \"undefined\"){\n                var vTo = force ? \"block\" : \"none\";\n                var vAlt = force ? \"none\" : \"block\";\n            }else{\n                var vTo = root.alt_content.style.display != \"block\" ? \"block\" : \"none\";\n                var vAlt = root.alt_content.style.display != \"block\" ? \"none\" : \"block\";\n            }\n            root.alt_content.style.display = vTo;\n            root.content.style.display = vAlt;\n        }\n        \n        root.toggleFooterVisibility = function(force){\n            if (typeof force != \"undefined\"){\n                var vTo = force ? \"block\" : \"none\";\n            }else{\n                var vTo = root.footer.style.display != \"block\" ? \"block\" : \"none\";\n            }\n            root.footer.style.display = vTo;\n        }\n        \n\t\troot.clear = function()\n\t\t{\n\t\t\tthis.content.innerHTML = \"\";\n\t\t}\n\n\t\troot.addHTML = function(code, classname, on_footer)\n\t\t{\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\tif(classname)\n\t\t\t\telem.className = classname;\n\t\t\telem.innerHTML = code;\n\t\t\tif(on_footer)\n\t\t\t\troot.footer.appendChild(elem);\n\t\t\telse\n\t\t\t\troot.content.appendChild(elem);\n\t\t\treturn elem;\n\t\t}\n\n\t\troot.addButton = function( name, callback, options )\n\t\t{\n\t\t\tvar elem = document.createElement(\"button\");\n\t\t\telem.innerText = name;\n\t\t\telem.options = options;\n\t\t\telem.classList.add(\"btn\");\n\t\t\telem.addEventListener(\"click\",callback);\n\t\t\troot.footer.appendChild(elem);\n\t\t\treturn elem;\n\t\t}\n\n\t\troot.addSeparator = function()\n\t\t{\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\telem.className = \"separator\";\n\t\t\troot.content.appendChild(elem);\n\t\t}\n\n\t\troot.addWidget = function( type, name, value, options, callback )\n\t\t{\n\t\t\toptions = options || {};\n\t\t\tvar str_value = String(value);\n\t\t\ttype = type.toLowerCase();\n\t\t\tif(type == \"number\")\n\t\t\t\tstr_value = value.toFixed(3);\n\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\telem.className = \"property\";\n\t\t\telem.innerHTML = \"<span class='property_name'></span><span class='property_value'></span>\";\n\t\t\telem.querySelector(\".property_name\").innerText = options.label || name;\n\t\t\tvar value_element = elem.querySelector(\".property_value\");\n\t\t\tvalue_element.innerText = str_value;\n\t\t\telem.dataset[\"property\"] = name;\n\t\t\telem.dataset[\"type\"] = options.type || type;\n\t\t\telem.options = options;\n\t\t\telem.value = value;\n\n\t\t\tif( type == \"code\" )\n\t\t\t\telem.addEventListener(\"click\", function(e){ root.inner_showCodePad( this.dataset[\"property\"] ); });\n\t\t\telse if (type == \"boolean\")\n\t\t\t{\n\t\t\t\telem.classList.add(\"boolean\");\n\t\t\t\tif(value)\n\t\t\t\t\telem.classList.add(\"bool-on\");\n\t\t\t\telem.addEventListener(\"click\", function(){ \n\t\t\t\t\t//var v = node.properties[this.dataset[\"property\"]]; \n\t\t\t\t\t//node.setProperty(this.dataset[\"property\"],!v); this.innerText = v ? \"true\" : \"false\"; \n\t\t\t\t\tvar propname = this.dataset[\"property\"];\n\t\t\t\t\tthis.value = !this.value;\n\t\t\t\t\tthis.classList.toggle(\"bool-on\");\n\t\t\t\t\tthis.querySelector(\".property_value\").innerText = this.value ? \"true\" : \"false\";\n\t\t\t\t\tinnerChange(propname, this.value );\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if (type == \"string\" || type == \"number\")\n\t\t\t{\n\t\t\t\tvalue_element.setAttribute(\"contenteditable\",true);\n\t\t\t\tvalue_element.addEventListener(\"keydown\", function(e){ \n\t\t\t\t\tif(e.code == \"Enter\" && (type != \"string\" || !e.shiftKey)) // allow for multiline\n\t\t\t\t\t{\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tthis.blur();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tvalue_element.addEventListener(\"blur\", function(){ \n\t\t\t\t\tvar v = this.innerText;\n\t\t\t\t\tvar propname = this.parentNode.dataset[\"property\"];\n\t\t\t\t\tvar proptype = this.parentNode.dataset[\"type\"];\n\t\t\t\t\tif( proptype == \"number\")\n\t\t\t\t\t\tv = Number(v);\n\t\t\t\t\tinnerChange(propname, v);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if (type == \"enum\" || type == \"combo\") {\n\t\t\t\tvar str_value = LGraphCanvas.getPropertyPrintableValue( value, options.values );\n\t\t\t\tvalue_element.innerText = str_value;\n\n\t\t\t\tvalue_element.addEventListener(\"click\", function(event){ \n\t\t\t\t\tvar values = options.values || [];\n\t\t\t\t\tvar propname = this.parentNode.dataset[\"property\"];\n\t\t\t\t\tvar elem_that = this;\n\t\t\t\t\tvar menu = new LiteGraph.ContextMenu(values,{\n\t\t\t\t\t\t\tevent: event,\n\t\t\t\t\t\t\tclassName: \"dark\",\n\t\t\t\t\t\t\tcallback: inner_clicked\n\t\t\t\t\t\t},\n\t\t\t\t\t\tref_window);\n\t\t\t\t\tfunction inner_clicked(v, option, event) {\n\t\t\t\t\t\t//node.setProperty(propname,v); \n\t\t\t\t\t\t//graphcanvas.dirty_canvas = true;\n\t\t\t\t\t\telem_that.innerText = v;\n\t\t\t\t\t\tinnerChange(propname,v);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t});\n            }\n\n\t\t\troot.content.appendChild(elem);\n\n\t\t\tfunction innerChange(name, value)\n\t\t\t{\n\t\t\t\t//console.log(\"change\",name,value);\n\t\t\t\t//that.dirty_canvas = true;\n\t\t\t\tif(options.callback)\n\t\t\t\t\toptions.callback(name,value,options);\n\t\t\t\tif(callback)\n\t\t\t\t\tcallback(name,value,options);\n\t\t\t}\n\n\t\t\treturn elem;\n\t\t}\n\n        if (root.onOpen && typeof root.onOpen == \"function\") root.onOpen();\n        \n\t\treturn root;\n\t};\n\n\tLGraphCanvas.getPropertyPrintableValue = function(value, values)\n\t{\n\t\tif(!values)\n\t\t\treturn String(value);\n\n\t\tif(values.constructor === Array)\n\t\t{\n\t\t\treturn String(value);\t\t\t\n\t\t}\n\n\t\tif(values.constructor === Object)\n\t\t{\n\t\t\tvar desc_value = \"\";\n\t\t\tfor(var k in values)\n\t\t\t{\n\t\t\t\tif(values[k] != value)\n\t\t\t\t\tcontinue;\n\t\t\t\tdesc_value = k;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn String(value) + \" (\"+desc_value+\")\";\n\t\t}\n\t}\n\n    LGraphCanvas.prototype.closePanels = function(){\n        var panel = document.querySelector(\"#node-panel\");\n\t\tif(panel)\n\t\t\tpanel.close();\n        var panel = document.querySelector(\"#option-panel\");\n\t\tif(panel)\n\t\t\tpanel.close();\n    }\n    \n    LGraphCanvas.prototype.showShowGraphOptionsPanel = function(refOpts, obEv, refMenu, refMenu2){\n        if(this.constructor && this.constructor.name == \"HTMLDivElement\"){\n            // assume coming from the menu event click\n            if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){\n                console.warn(\"Canvas not found\"); // need a ref to canvas obj\n                /*console.debug(event);\n                console.debug(event.target);*/\n                return;\n            }\n            var graphcanvas = obEv.event.target.lgraphcanvas;\n        }else{\n            // assume called internally\n            var graphcanvas = this;\n        }\n        graphcanvas.closePanels();\n        var ref_window = graphcanvas.getCanvasWindow();\n        panel = graphcanvas.createPanel(\"Options\",{\n                                            closable: true\n                                            ,window: ref_window\n                                            ,onOpen: function(){\n                                                graphcanvas.OPTIONPANEL_IS_OPEN = true;\n                                            }\n                                            ,onClose: function(){\n                                                graphcanvas.OPTIONPANEL_IS_OPEN = false;\n                                                graphcanvas.options_panel = null;\n                                            }\n                                        });\n        graphcanvas.options_panel = panel;\n        panel.id = \"option-panel\";\n\t\tpanel.classList.add(\"settings\");\n        \n        function inner_refresh(){\n            \n            panel.content.innerHTML = \"\"; //clear\n\n            var fUpdate = function(name, value, options){\n                switch(name){\n                    /*case \"Render mode\":\n                        // Case \"\".. \n                        if (options.values && options.key){\n                            var kV = Object.values(options.values).indexOf(value);\n                            if (kV>=0 && options.values[kV]){\n                                console.debug(\"update graph options: \"+options.key+\": \"+kV);\n                                graphcanvas[options.key] = kV;\n                                //console.debug(graphcanvas);\n                                break;\n                            }\n                        }\n                        console.warn(\"unexpected options\");\n                        console.debug(options);\n                        break;*/\n                    default:\n                        //console.debug(\"want to update graph options: \"+name+\": \"+value);\n                        if (options && options.key){\n                            name = options.key;\n                        }\n                        if (options.values){\n                            value = Object.values(options.values).indexOf(value);\n                        }\n                        //console.debug(\"update graph option: \"+name+\": \"+value);\n                        graphcanvas[name] = value;\n                        break;\n                }\n            };\n            \n            // panel.addWidget( \"string\", \"Graph name\", \"\", {}, fUpdate); // implement\n            \n            var aProps = LiteGraph.availableCanvasOptions;\n            aProps.sort();\n            for(var pI in aProps){\n                var pX = aProps[pI];\n                panel.addWidget( \"boolean\", pX, graphcanvas[pX], {key: pX, on: \"True\", off: \"False\"}, fUpdate);\n            }\n            \n            var aLinks = [ graphcanvas.links_render_mode ];\n            panel.addWidget( \"combo\", \"Render mode\", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: \"links_render_mode\", values: LiteGraph.LINK_RENDER_MODES}, fUpdate);\n            \n            panel.addSeparator();\n            \n            panel.footer.innerHTML = \"\"; // clear\n\n\t\t}\n        inner_refresh();\n\n\t\tgraphcanvas.canvas.parentNode.appendChild( panel );\n    }\n    \n    LGraphCanvas.prototype.showShowNodePanel = function( node )\n\t{\n\t\tthis.SELECTED_NODE = node;\n\t\tthis.closePanels();\n\t\tvar ref_window = this.getCanvasWindow();\n        var that = this;\n\t\tvar graphcanvas = this;\n\t\tvar panel = this.createPanel(node.title || \"\",{\n                                                    closable: true\n                                                    ,window: ref_window\n                                                    ,onOpen: function(){\n                                                        graphcanvas.NODEPANEL_IS_OPEN = true;\n                                                    }\n                                                    ,onClose: function(){\n                                                        graphcanvas.NODEPANEL_IS_OPEN = false;\n                                                        graphcanvas.node_panel = null;\n                                                    }\n                                                });\n        graphcanvas.node_panel = panel;\n\t\tpanel.id = \"node-panel\";\n\t\tpanel.node = node;\n\t\tpanel.classList.add(\"settings\");\n\n\t\tfunction inner_refresh()\n\t\t{\n\t\t\tpanel.content.innerHTML = \"\"; //clear\n\t\t\tpanel.addHTML(\"<span class='node_type'>\"+node.type+\"</span><span class='node_desc'>\"+(node.constructor.desc || \"\")+\"</span><span class='separator'></span>\");\n\n\t\t\tpanel.addHTML(\"<h3>Properties</h3>\");\n\n            var fUpdate = function(name,value){\n                            graphcanvas.graph.beforeChange(node);\n                            switch(name){\n                                case \"Title\":\n                                    node.title = value;\n                                    break;\n                                case \"Mode\":\n                                    var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value);\n                                    if (kV>=0 && LiteGraph.NODE_MODES[kV]){\n                                        node.changeMode(kV);\n                                    }else{\n                                        console.warn(\"unexpected mode: \"+value);\n                                    }\n                                    break;\n                                case \"Color\":\n                                    if (LGraphCanvas.node_colors[value]){\n                                        node.color = LGraphCanvas.node_colors[value].color;\n                                        node.bgcolor = LGraphCanvas.node_colors[value].bgcolor;\n                                    }else{\n                                        console.warn(\"unexpected color: \"+value);\n                                    }\n                                    break;\n                                default:\n                                    node.setProperty(name,value);\n                                    break;\n                            }\n                            graphcanvas.graph.afterChange();\n                            graphcanvas.dirty_canvas = true;\n                        };\n            \n            panel.addWidget( \"string\", \"Title\", node.title, {}, fUpdate);\n            \n            panel.addWidget( \"combo\", \"Mode\", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate);\n            \n            var nodeCol = \"\";\n            if (node.color !== undefined){\n                nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; });\n            }\n            \n            panel.addWidget( \"combo\", \"Color\", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate);\n            \n            for(var pName in node.properties)\n\t\t\t{\n\t\t\t\tvar value = node.properties[pName];\n\t\t\t\tvar info = node.getPropertyInfo(pName);\n\t\t\t\tvar type = info.type || \"string\";\n\n\t\t\t\t//in case the user wants control over the side panel widget\n\t\t\t\tif( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tpanel.addWidget( info.widget || info.type, pName, value, info, fUpdate);\n\t\t\t}\n\n\t\t\tpanel.addSeparator();\n\n\t\t\tif(node.onShowCustomPanelInfo)\n\t\t\t\tnode.onShowCustomPanelInfo(panel);\n\n            panel.footer.innerHTML = \"\"; // clear\n\t\t\tpanel.addButton(\"Delete\",function(){\n\t\t\t\tif(node.block_delete)\n\t\t\t\t\treturn;\n\t\t\t\tnode.graph.remove(node);\n\t\t\t\tpanel.close();\n\t\t\t}).classList.add(\"delete\");\n\t\t}\n\n\t\tpanel.inner_showCodePad = function( propname )\n\t\t{\n            panel.classList.remove(\"settings\");\n            panel.classList.add(\"centered\");\n\n            \n\t\t\t/*if(window.CodeFlask) //disabled for now\n\t\t\t{\n\t\t\t\tpanel.content.innerHTML = \"<div class='code'></div>\";\n\t\t\t\tvar flask = new CodeFlask( \"div.code\", { language: 'js' });\n\t\t\t\tflask.updateCode(node.properties[propname]);\n\t\t\t\tflask.onUpdate( function(code) {\n\t\t\t\t\tnode.setProperty(propname, code);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse\n\t\t\t{*/\n\t\t\t\tpanel.alt_content.innerHTML = \"<textarea class='code'></textarea>\";\n\t\t\t\tvar textarea = panel.alt_content.querySelector(\"textarea\");\n                var fDoneWith = function(){\n                    panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = \"block\"; // panel.close();\n                    panel.toggleFooterVisibility(true);\n                    textarea.parentNode.removeChild(textarea);\n                    panel.classList.add(\"settings\");\n                    panel.classList.remove(\"centered\");\n                    inner_refresh();\n                }\n\t\t\t\ttextarea.value = node.properties[propname];\n\t\t\t\ttextarea.addEventListener(\"keydown\", function(e){\n\t\t\t\t\tif(e.code == \"Enter\" && e.ctrlKey )\n\t\t\t\t\t{\n\t\t\t\t\t\tnode.setProperty(propname, textarea.value);\n                        fDoneWith();\n\t\t\t\t\t}\n\t\t\t\t});\n                panel.toggleAltContent(true);\n                panel.toggleFooterVisibility(false);\n\t\t\t\ttextarea.style.height = \"calc(100% - 40px)\";\n\t\t\t/*}*/\n\t\t\tvar assign = panel.addButton( \"Assign\", function(){\n\t\t\t\tnode.setProperty(propname, textarea.value);\n                fDoneWith();\n\t\t\t});\n\t\t\tpanel.alt_content.appendChild(assign); //panel.content.appendChild(assign);\n\t\t\tvar button = panel.addButton( \"Close\", fDoneWith);\n\t\t\tbutton.style.float = \"right\";\n\t\t\tpanel.alt_content.appendChild(button); // panel.content.appendChild(button);\n\t\t}\n\n\t\tinner_refresh();\n\n\t\tthis.canvas.parentNode.appendChild( panel );\n\t}\n\t\n\tLGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node)\n\t{\n\t\tconsole.log(\"showing subgraph properties dialog\");\n\n\t\tvar old_panel = this.canvas.parentNode.querySelector(\".subgraph_dialog\");\n\t\tif(old_panel)\n\t\t\told_panel.close();\n\n\t\tvar panel = this.createPanel(\"Subgraph Inputs\",{closable:true, width: 500});\n\t\tpanel.node = node;\n\t\tpanel.classList.add(\"subgraph_dialog\");\n\n\t\tfunction inner_refresh()\n\t\t{\n\t\t\tpanel.clear();\n\n\t\t\t//show currents\n\t\t\tif(node.inputs)\n\t\t\t\tfor(var i = 0; i < node.inputs.length; ++i)\n\t\t\t\t{\n\t\t\t\t\tvar input = node.inputs[i];\n\t\t\t\t\tif(input.not_subgraph_input)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tvar html = \"<button>&#10005;</button> <span class='bullet_icon'></span><span class='name'></span><span class='type'></span>\";\n\t\t\t\t\tvar elem = panel.addHTML(html,\"subgraph_property\");\n\t\t\t\t\telem.dataset[\"name\"] = input.name;\n\t\t\t\t\telem.dataset[\"slot\"] = i;\n\t\t\t\t\telem.querySelector(\".name\").innerText = input.name;\n\t\t\t\t\telem.querySelector(\".type\").innerText = input.type;\n\t\t\t\t\telem.querySelector(\"button\").addEventListener(\"click\",function(e){\n\t\t\t\t\t\tnode.removeInput( Number( this.parentNode.dataset[\"slot\"] ) );\n\t\t\t\t\t\tinner_refresh();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\n\t\t//add extra\n\t\tvar html = \" + <span class='label'>Name</span><input class='name'/><span class='label'>Type</span><input class='type'></input><button>+</button>\";\n\t\tvar elem = panel.addHTML(html,\"subgraph_property extra\", true);\n\t\telem.querySelector(\"button\").addEventListener(\"click\", function(e){\n\t\t\tvar elem = this.parentNode;\n\t\t\tvar name = elem.querySelector(\".name\").value;\n\t\t\tvar type = elem.querySelector(\".type\").value;\n\t\t\tif(!name || node.findInputSlot(name) != -1)\n\t\t\t\treturn;\n\t\t\tnode.addInput(name,type);\n\t\t\telem.querySelector(\".name\").value = \"\";\n\t\t\telem.querySelector(\".type\").value = \"\";\n\t\t\tinner_refresh();\n\t\t});\n\n\t\tinner_refresh();\n\t    this.canvas.parentNode.appendChild(panel);\n\t\treturn panel;\n\t}\n    LGraphCanvas.prototype.showSubgraphPropertiesDialogRight = function (node) {\n\n        // console.log(\"showing subgraph properties dialog\");\n        var that = this;\n        // old_panel if old_panel is exist close it\n        var old_panel = this.canvas.parentNode.querySelector(\".subgraph_dialog\");\n        if (old_panel)\n            old_panel.close();\n        // new panel\n        var panel = this.createPanel(\"Subgraph Outputs\", { closable: true, width: 500 });\n        panel.node = node;\n        panel.classList.add(\"subgraph_dialog\");\n\n        function inner_refresh() {\n            panel.clear();\n            //show currents\n            if (node.outputs)\n                for (var i = 0; i < node.outputs.length; ++i) {\n                    var input = node.outputs[i];\n                    if (input.not_subgraph_output)\n                        continue;\n                    var html = \"<button>&#10005;</button> <span class='bullet_icon'></span><span class='name'></span><span class='type'></span>\";\n                    var elem = panel.addHTML(html, \"subgraph_property\");\n                    elem.dataset[\"name\"] = input.name;\n                    elem.dataset[\"slot\"] = i;\n                    elem.querySelector(\".name\").innerText = input.name;\n                    elem.querySelector(\".type\").innerText = input.type;\n                    elem.querySelector(\"button\").addEventListener(\"click\", function (e) {\n                        node.removeOutput(Number(this.parentNode.dataset[\"slot\"]));\n                        inner_refresh();\n                    });\n                }\n        }\n\n        //add extra\n        var html = \" + <span class='label'>Name</span><input class='name'/><span class='label'>Type</span><input class='type'></input><button>+</button>\";\n        var elem = panel.addHTML(html, \"subgraph_property extra\", true);\n        elem.querySelector(\".name\").addEventListener(\"keydown\", function (e) {\n            if (e.keyCode == 13) {\n                addOutput.apply(this)\n            }\n        })\n        elem.querySelector(\"button\").addEventListener(\"click\", function (e) {\n            addOutput.apply(this)\n        });\n        function addOutput() {\n            var elem = this.parentNode;\n            var name = elem.querySelector(\".name\").value;\n            var type = elem.querySelector(\".type\").value;\n            if (!name || node.findOutputSlot(name) != -1)\n                return;\n            node.addOutput(name, type);\n            elem.querySelector(\".name\").value = \"\";\n            elem.querySelector(\".type\").value = \"\";\n            inner_refresh();\n        }\n\n        inner_refresh();\n        this.canvas.parentNode.appendChild(panel);\n        return panel;\n    }\n\tLGraphCanvas.prototype.checkPanels = function()\n\t{\n\t\tif(!this.canvas)\n\t\t\treturn;\n\t\tvar panels = this.canvas.parentNode.querySelectorAll(\".litegraph.dialog\");\n\t\tfor(var i = 0; i < panels.length; ++i)\n\t\t{\n\t\t\tvar panel = panels[i];\n\t\t\tif( !panel.node )\n\t\t\t\tcontinue;\n\t\t\tif( !panel.node.graph || panel.graph != this.graph )\n\t\t\t\tpanel.close();\n\t\t}\n\t}\n\n    LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) {\n\t\tnode.graph.beforeChange(/*?*/);\n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tnode.collapse();\n\t\t}\n\t\t\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tnode.graph.afterChange(/*?*/);\n    };\n\n    LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) {\n        node.pin();\n    };\n\n    LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) {\n        new LiteGraph.ContextMenu(\n            LiteGraph.NODE_MODES,\n            { event: e, callback: inner_clicked, parentMenu: menu, node: node }\n        );\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n            var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v);\n            var fApplyMultiNode = function(node){\n\t\t\t\tif (kV>=0 && LiteGraph.NODE_MODES[kV])\n\t\t\t\t\tnode.changeMode(kV);\n\t\t\t\telse{\n\t\t\t\t\tconsole.warn(\"unexpected mode: \"+v);\n\t\t\t\t\tnode.changeMode(LiteGraph.ALWAYS);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyMultiNode(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node for color\";\n        }\n\n        var values = [];\n        values.push({\n            value: null,\n            content:\n                \"<span style='display: block; padding-left: 4px;'>No color</span>\"\n        });\n\n        for (var i in LGraphCanvas.node_colors) {\n            var color = LGraphCanvas.node_colors[i];\n            var value = {\n                value: i,\n                content:\n                    \"<span style='display: block; color: #999; padding-left: 4px; border-left: 8px solid \" +\n                    color.color +\n                    \"; background-color:\" +\n                    color.bgcolor +\n                    \"'>\" +\n                    i +\n                    \"</span>\"\n            };\n            values.push(value);\n        }\n        new LiteGraph.ContextMenu(values, {\n            event: e,\n            callback: inner_clicked,\n            parentMenu: menu,\n            node: node\n        });\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n\n            var color = v.value ? LGraphCanvas.node_colors[v.value] : null;\n\t\t\t\n\t\t\tvar fApplyColor = function(node){\n\t\t\t\tif (color) {\n\t\t\t\t\tif (node.constructor === LiteGraph.LGraphGroup) {\n\t\t\t\t\t\tnode.color = color.groupcolor;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnode.color = color.color;\n\t\t\t\t\t\tnode.bgcolor = color.bgcolor;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tdelete node.color;\n\t\t\t\t\tdelete node.bgcolor;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyColor(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyColor(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n            node.setDirtyCanvas(true, true);\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node passed\";\n        }\n\n        new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, {\n            event: e,\n            callback: inner_clicked,\n            parentMenu: menu,\n            node: node\n        });\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n\t\t\tnode.graph.beforeChange(/*?*/); //node\n            \n\t\t\tvar fApplyMultiNode = function(node){\n\t\t\t\tnode.shape = v;\n\t\t\t}\n\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyMultiNode(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tnode.graph.afterChange(/*?*/); //node\n            node.setDirtyCanvas(true);\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node passed\";\n        }\n\n\t\tvar graph = node.graph;\n\t\tgraph.beforeChange();\n        \n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tif (node.removable === false) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tgraph.remove(node);\n\t\t}\n\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tgraph.afterChange();\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) {\n\t\tvar graph = node.graph;\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif(!graphcanvas) //??\n\t\t\treturn;\n\n\t\tvar nodes_list = Object.values( graphcanvas.selected_nodes || {} );\n\t\tif( !nodes_list.length )\n\t\t\tnodes_list = [ node ];\n\n\t\tvar subgraph_node = LiteGraph.createNode(\"graph/subgraph\");\n\t\tsubgraph_node.pos = node.pos.concat();\n\t\tgraph.add(subgraph_node);\n\n\t\tsubgraph_node.buildFromNodes( nodes_list );\n\n\t\tgraphcanvas.deselectAllNodes();\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) {\n        \n\t\tnode.graph.beforeChange();\n        \n\t\tvar newSelected = {};\n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tif (node.clonable === false) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar newnode = node.clone();\n\t\t\tif (!newnode) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tnewnode.pos = [node.pos[0] + 5, node.pos[1] + 5];\n\t\t\tnode.graph.add(newnode);\n\t\t\tnewSelected[newnode.id] = newnode;\n\t\t}\n\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tif(Object.keys(newSelected).length){\n\t\t\tgraphcanvas.selectNodes(newSelected);\n\t\t}\n\t\t\n\t\tnode.graph.afterChange();\n\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.node_colors = {\n        red: { color: \"#322\", bgcolor: \"#533\", groupcolor: \"#A88\" },\n        brown: { color: \"#332922\", bgcolor: \"#593930\", groupcolor: \"#b06634\" },\n        green: { color: \"#232\", bgcolor: \"#353\", groupcolor: \"#8A8\" },\n        blue: { color: \"#223\", bgcolor: \"#335\", groupcolor: \"#88A\" },\n        pale_blue: {\n            color: \"#2a363b\",\n            bgcolor: \"#3f5159\",\n            groupcolor: \"#3f789e\"\n        },\n        cyan: { color: \"#233\", bgcolor: \"#355\", groupcolor: \"#8AA\" },\n        purple: { color: \"#323\", bgcolor: \"#535\", groupcolor: \"#a1309b\" },\n        yellow: { color: \"#432\", bgcolor: \"#653\", groupcolor: \"#b58b2a\" },\n        black: { color: \"#222\", bgcolor: \"#000\", groupcolor: \"#444\" }\n    };\n\n    LGraphCanvas.prototype.getCanvasMenuOptions = function() {\n        var options = null;\n\t\tvar that = this;\n        if (this.getMenuOptions) {\n            options = this.getMenuOptions();\n        } else {\n            options = [\n                {\n                    content: \"Add Node\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuAdd\n                },\n                { content: \"Add Group\", callback: LGraphCanvas.onGroupAdd },\n\t\t\t\t//{ content: \"Arrange\", callback: that.graph.arrange },\n                //{content:\"Collapse All\", callback: LGraphCanvas.onMenuCollapseAll }\n            ];\n            /*if (LiteGraph.showCanvasOptions){\n                options.push({ content: \"Options\", callback: that.showShowGraphOptionsPanel });\n            }*/\n\n            if (Object.keys(this.selected_nodes).length > 1) {\n                options.push({\n                    content: \"Align\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onGroupAlign,\n                })\n            }\n\n            if (this._graph_stack && this._graph_stack.length > 0) {\n                options.push(null, {\n                    content: \"Close subgraph\",\n                    callback: this.closeSubgraph.bind(this)\n                });\n            }\n        }\n\n        if (this.getExtraMenuOptions) {\n            var extra = this.getExtraMenuOptions(this, options);\n            if (extra) {\n                options = options.concat(extra);\n            }\n        }\n\n        return options;\n    };\n\n    //called by processContextMenu to extract the menu list\n    LGraphCanvas.prototype.getNodeMenuOptions = function(node) {\n        var options = null;\n\n        if (node.getMenuOptions) {\n            options = node.getMenuOptions(this);\n        } else {\n            options = [\n                {\n                    content: \"Inputs\",\n                    has_submenu: true,\n                    disabled: true,\n                    callback: LGraphCanvas.showMenuNodeOptionalInputs\n                },\n                {\n                    content: \"Outputs\",\n                    has_submenu: true,\n                    disabled: true,\n                    callback: LGraphCanvas.showMenuNodeOptionalOutputs\n                },\n                null,\n                {\n                    content: \"Properties\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onShowMenuNodeProperties\n                },\n                null,\n                {\n                    content: \"Title\",\n                    callback: LGraphCanvas.onShowPropertyEditor\n                },\n                {\n                    content: \"Mode\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeMode\n                }];\n            if(node.resizable !== false){\n                options.push({\n                    content: \"Resize\", callback: LGraphCanvas.onMenuResizeNode\n                });\n            }\n            options.push(\n                {\n                    content: \"Collapse\",\n                    callback: LGraphCanvas.onMenuNodeCollapse\n                },\n                { content: \"Pin\", callback: LGraphCanvas.onMenuNodePin },\n                {\n                    content: \"Colors\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeColors\n                },\n                {\n                    content: \"Shapes\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeShapes\n                },\n                null\n            );\n        }\n\n        if (node.onGetInputs) {\n            var inputs = node.onGetInputs();\n            if (inputs && inputs.length) {\n                options[0].disabled = false;\n            }\n        }\n\n        if (node.onGetOutputs) {\n            var outputs = node.onGetOutputs();\n            if (outputs && outputs.length) {\n                options[1].disabled = false;\n            }\n        }\n\n        if (node.getExtraMenuOptions) {\n            var extra = node.getExtraMenuOptions(this, options);\n            if (extra) {\n                extra.push(null);\n                options = extra.concat(options);\n            }\n        }\n\n        if (node.clonable !== false) {\n            options.push({\n                content: \"Clone\",\n                callback: LGraphCanvas.onMenuNodeClone\n            });\n        }\n\n\t\tif(0) //TODO\n\t\toptions.push({\n\t\t\tcontent: \"To Subgraph\",\n\t\t\tcallback: LGraphCanvas.onMenuNodeToSubgraph\n\t\t});\n\n        if (Object.keys(this.selected_nodes).length > 1) {\n            options.push({\n                content: \"Align Selected To\",\n                has_submenu: true,\n                callback: LGraphCanvas.onNodeAlign,\n            })\n        }\n\n\t\toptions.push(null, {\n\t\t\tcontent: \"Remove\",\n\t\t\tdisabled: !(node.removable !== false && !node.block_delete ),\n\t\t\tcallback: LGraphCanvas.onMenuNodeRemove\n\t\t});\n\n        if (node.graph && node.graph.onGetNodeMenuOptions) {\n            node.graph.onGetNodeMenuOptions(options, node);\n        }\n\n        return options;\n    };\n\n    LGraphCanvas.prototype.getGroupMenuOptions = function(node) {\n        var o = [\n            { content: \"Title\", callback: LGraphCanvas.onShowPropertyEditor },\n            {\n                content: \"Color\",\n                has_submenu: true,\n                callback: LGraphCanvas.onMenuNodeColors\n            },\n            {\n                content: \"Font size\",\n                property: \"font_size\",\n                type: \"Number\",\n                callback: LGraphCanvas.onShowPropertyEditor\n            },\n            null,\n            { content: \"Remove\", callback: LGraphCanvas.onMenuNodeRemove }\n        ];\n\n        return o;\n    };\n\n    LGraphCanvas.prototype.processContextMenu = function(node, event) {\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var menu_info = null;\n        var options = {\n            event: event,\n            callback: inner_option_clicked,\n            extra: node\n        };\n\n\t\tif(node)\n\t\t\toptions.title = node.type;\n\n        //check if mouse is in input\n        var slot = null;\n        if (node) {\n            slot = node.getSlotInPosition(event.canvasX, event.canvasY);\n            LGraphCanvas.active_node = node;\n        }\n\n        if (slot) {\n            //on slot\n            menu_info = [];\n            if (node.getSlotMenuOptions) {\n                menu_info = node.getSlotMenuOptions(slot);\n            } else {\n                if (\n                    slot &&\n                    slot.output &&\n                    slot.output.links &&\n                    slot.output.links.length\n                ) {\n                    menu_info.push({ content: \"Disconnect Links\", slot: slot });\n                }\n                var _slot = slot.input || slot.output;\n                if (_slot.removable){\n                \tmenu_info.push(\n\t                    _slot.locked\n\t                        ? \"Cannot remove\"\n\t                        : { content: \"Remove Slot\", slot: slot }\n\t                );\n            \t}\n                if (!_slot.nameLocked){\n\t                menu_info.push({ content: \"Rename Slot\", slot: slot });\n                }\n    \n            }\n            options.title =\n                (slot.input ? slot.input.type : slot.output.type) || \"*\";\n            if (slot.input && slot.input.type == LiteGraph.ACTION) {\n                options.title = \"Action\";\n            }\n            if (slot.output && slot.output.type == LiteGraph.EVENT) {\n                options.title = \"Event\";\n            }\n        } else {\n            if (node) {\n                //on node\n                menu_info = this.getNodeMenuOptions(node);\n            } else {\n                menu_info = this.getCanvasMenuOptions();\n                var group = this.graph.getGroupOnPos(\n                    event.canvasX,\n                    event.canvasY\n                );\n                if (group) {\n                    //on group\n                    menu_info.push(null, {\n                        content: \"Edit Group\",\n                        has_submenu: true,\n                        submenu: {\n                            title: \"Group\",\n                            extra: group,\n                            options: this.getGroupMenuOptions(group)\n                        }\n                    });\n                }\n            }\n        }\n\n        //show menu\n        if (!menu_info) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window);\n\n        function inner_option_clicked(v, options, e) {\n            if (!v) {\n                return;\n            }\n\n            if (v.content == \"Remove Slot\") {\n                var info = v.slot;\n                node.graph.beforeChange();\n                if (info.input) {\n                    node.removeInput(info.slot);\n                } else if (info.output) {\n                    node.removeOutput(info.slot);\n                }\n                node.graph.afterChange();\n                return;\n            } else if (v.content == \"Disconnect Links\") {\n                var info = v.slot;\n                node.graph.beforeChange();\n                if (info.output) {\n                    node.disconnectOutput(info.slot);\n                } else if (info.input) {\n                    node.disconnectInput(info.slot);\n                }\n                node.graph.afterChange();\n                return;\n            } else if (v.content == \"Rename Slot\") {\n                var info = v.slot;\n                var slot_info = info.input\n                    ? node.getInputInfo(info.slot)\n                    : node.getOutputInfo(info.slot);\n                var dialog = that.createDialog(\n                    \"<span class='name'>Name</span><input autofocus type='text'/><button>OK</button>\",\n                    options\n                );\n                var input = dialog.querySelector(\"input\");\n                if (input && slot_info) {\n                    input.value = slot_info.label || \"\";\n                }\n                var inner = function(){\n                \tnode.graph.beforeChange();\n                    if (input.value) {\n                        if (slot_info) {\n                            slot_info.label = input.value;\n                        }\n                        that.setDirty(true);\n                    }\n                    dialog.close();\n                    node.graph.afterChange();\n                }\n                dialog.querySelector(\"button\").addEventListener(\"click\", inner);\n                input.addEventListener(\"keydown\", function(e) {\n                    dialog.is_modified = true;\n                    if (e.keyCode == 27) {\n                        //ESC\n                        dialog.close();\n                    } else if (e.keyCode == 13) {\n                        inner(); // save\n                    } else if (e.keyCode != 13 && e.target.localName != \"textarea\") {\n                        return;\n                    }\n                    e.preventDefault();\n                    e.stopPropagation();\n                });\n                input.focus();\n            }\n\n            //if(v.callback)\n            //\treturn v.callback.call(that, node, options, e, menu, that, event );\n        }\n    };\n\n    //API *************************************************\n    function compareObjects(a, b) {\n        for (var i in a) {\n            if (a[i] != b[i]) {\n                return false;\n            }\n        }\n        return true;\n    }\n    LiteGraph.compareObjects = compareObjects;\n\n    function distance(a, b) {\n        return Math.sqrt(\n            (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])\n        );\n    }\n    LiteGraph.distance = distance;\n\n    function colorToString(c) {\n        return (\n            \"rgba(\" +\n            Math.round(c[0] * 255).toFixed() +\n            \",\" +\n            Math.round(c[1] * 255).toFixed() +\n            \",\" +\n            Math.round(c[2] * 255).toFixed() +\n            \",\" +\n            (c.length == 4 ? c[3].toFixed(2) : \"1.0\") +\n            \")\"\n        );\n    }\n    LiteGraph.colorToString = colorToString;\n\n    function isInsideRectangle(x, y, left, top, width, height) {\n        if (left < x && left + width > x && top < y && top + height > y) {\n            return true;\n        }\n        return false;\n    }\n    LiteGraph.isInsideRectangle = isInsideRectangle;\n\n    //[minx,miny,maxx,maxy]\n    function growBounding(bounding, x, y) {\n        if (x < bounding[0]) {\n            bounding[0] = x;\n        } else if (x > bounding[2]) {\n            bounding[2] = x;\n        }\n\n        if (y < bounding[1]) {\n            bounding[1] = y;\n        } else if (y > bounding[3]) {\n            bounding[3] = y;\n        }\n    }\n    LiteGraph.growBounding = growBounding;\n\n    //point inside bounding box\n    function isInsideBounding(p, bb) {\n        if (\n            p[0] < bb[0][0] ||\n            p[1] < bb[0][1] ||\n            p[0] > bb[1][0] ||\n            p[1] > bb[1][1]\n        ) {\n            return false;\n        }\n        return true;\n    }\n    LiteGraph.isInsideBounding = isInsideBounding;\n\n    //bounding overlap, format: [ startx, starty, width, height ]\n    function overlapBounding(a, b) {\n        var A_end_x = a[0] + a[2];\n        var A_end_y = a[1] + a[3];\n        var B_end_x = b[0] + b[2];\n        var B_end_y = b[1] + b[3];\n\n        if (\n            a[0] > B_end_x ||\n            a[1] > B_end_y ||\n            A_end_x < b[0] ||\n            A_end_y < b[1]\n        ) {\n            return false;\n        }\n        return true;\n    }\n    LiteGraph.overlapBounding = overlapBounding;\n\n    //Convert a hex value to its decimal value - the inputted hex must be in the\n    //\tformat of a hex triplet - the kind we use for HTML colours. The function\n    //\twill return an array with three values.\n    function hex2num(hex) {\n        if (hex.charAt(0) == \"#\") {\n            hex = hex.slice(1);\n        } //Remove the '#' char - if there is one.\n        hex = hex.toUpperCase();\n        var hex_alphabets = \"0123456789ABCDEF\";\n        var value = new Array(3);\n        var k = 0;\n        var int1, int2;\n        for (var i = 0; i < 6; i += 2) {\n            int1 = hex_alphabets.indexOf(hex.charAt(i));\n            int2 = hex_alphabets.indexOf(hex.charAt(i + 1));\n            value[k] = int1 * 16 + int2;\n            k++;\n        }\n        return value;\n    }\n\n    LiteGraph.hex2num = hex2num;\n\n    //Give a array with three values as the argument and the function will return\n    //\tthe corresponding hex triplet.\n    function num2hex(triplet) {\n        var hex_alphabets = \"0123456789ABCDEF\";\n        var hex = \"#\";\n        var int1, int2;\n        for (var i = 0; i < 3; i++) {\n            int1 = triplet[i] / 16;\n            int2 = triplet[i] % 16;\n\n            hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2);\n        }\n        return hex;\n    }\n\n    LiteGraph.num2hex = num2hex;\n\n    /* LiteGraph GUI elements used for canvas editing *************************************/\n\n    /**\n     * ContextMenu from LiteGUI\n     *\n     * @class ContextMenu\n     * @constructor\n     * @param {Array} values (allows object { title: \"Nice text\", callback: function ... })\n     * @param {Object} options [optional] Some options:\\\n     * - title: title to show on top of the menu\n     * - callback: function to call when an option is clicked, it receives the item information\n     * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback\n     * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position\n     */\n    function ContextMenu(values, options) {\n        options = options || {};\n        this.options = options;\n        var that = this;\n\n        //to link a menu with its parent\n        if (options.parentMenu) {\n            if (options.parentMenu.constructor !== this.constructor) {\n                console.error(\n                    \"parentMenu must be of class ContextMenu, ignoring it\"\n                );\n                options.parentMenu = null;\n            } else {\n                this.parentMenu = options.parentMenu;\n                this.parentMenu.lock = true;\n                this.parentMenu.current_submenu = this;\n            }\n        }\n\n\t\tvar eventClass = null;\n\t\tif(options.event) //use strings because comparing classes between windows doesnt work\n\t\t\teventClass = options.event.constructor.name;\n        if ( eventClass !== \"MouseEvent\" &&\n            eventClass !== \"CustomEvent\" &&\n\t\t\teventClass !== \"PointerEvent\"\n        ) {\n            console.error(\n                \"Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. (\"+eventClass+\")\"\n            );\n            options.event = null;\n        }\n\n        var root = document.createElement(\"div\");\n        root.className = \"litegraph litecontextmenu litemenubar-panel\";\n        if (options.className) {\n            root.className += \" \" + options.className;\n        }\n        root.style.minWidth = 100;\n        root.style.minHeight = 100;\n        root.style.pointerEvents = \"none\";\n        setTimeout(function() {\n            root.style.pointerEvents = \"auto\";\n        }, 100); //delay so the mouse up event is not caught by this element\n\n        //this prevents the default context browser menu to open in case this menu was created when pressing right button\n\t\tLiteGraph.pointerListenerAdd(root,\"up\",\n            function(e) {\n\t\t\t  \t//console.log(\"pointerevents: ContextMenu up root prevent\");\n                e.preventDefault();\n                return true;\n            },\n            true\n        );\n        root.addEventListener(\n            \"contextmenu\",\n            function(e) {\n                if (e.button != 2) {\n                    //right button\n                    return false;\n                }\n                e.preventDefault();\n                return false;\n            },\n            true\n        );\n\n        LiteGraph.pointerListenerAdd(root,\"down\",\n            function(e) {\n\t\t\t  \t//console.log(\"pointerevents: ContextMenu down\");\n                if (e.button == 2) {\n                    that.close();\n                    e.preventDefault();\n                    return true;\n                }\n            },\n            true\n        );\n\n        function on_mouse_wheel(e) {\n            var pos = parseInt(root.style.top);\n            root.style.top =\n                (pos + e.deltaY * options.scroll_speed).toFixed() + \"px\";\n            e.preventDefault();\n            return true;\n        }\n\n        if (!options.scroll_speed) {\n            options.scroll_speed = 0.1;\n        }\n\n        root.addEventListener(\"wheel\", on_mouse_wheel, true);\n        root.addEventListener(\"mousewheel\", on_mouse_wheel, true);\n\n        this.root = root;\n\n        //title\n        if (options.title) {\n            var element = document.createElement(\"div\");\n            element.className = \"litemenu-title\";\n            element.innerHTML = options.title;\n            root.appendChild(element);\n        }\n\n        //entries\n        var num = 0;\n        for (var i=0; i < values.length; i++) {\n            var name = values.constructor == Array ? values[i] : i;\n            if (name != null && name.constructor !== String) {\n                name = name.content === undefined ? String(name) : name.content;\n            }\n            var value = values[i];\n            this.addItem(name, value, options);\n            num++;\n        }\n\n        //close on leave? touch enabled devices won't work TODO use a global device detector and condition on that\n        /*LiteGraph.pointerListenerAdd(root,\"leave\", function(e) {\n\t\t  \tconsole.log(\"pointerevents: ContextMenu leave\");\n            if (that.lock) {\n                return;\n            }\n            if (root.closing_timer) {\n                clearTimeout(root.closing_timer);\n            }\n            root.closing_timer = setTimeout(that.close.bind(that, e), 500);\n            //that.close(e);\n        });*/\n\n\t\tLiteGraph.pointerListenerAdd(root,\"enter\", function(e) {\n\t\t  \t//console.log(\"pointerevents: ContextMenu enter\");\n            if (root.closing_timer) {\n                clearTimeout(root.closing_timer);\n            }\n        });\n\n        //insert before checking position\n        var root_document = document;\n        if (options.event) {\n            root_document = options.event.target.ownerDocument;\n        }\n\n        if (!root_document) {\n            root_document = document;\n        }\n\n\t\tif( root_document.fullscreenElement )\n\t        root_document.fullscreenElement.appendChild(root);\n\t\telse\n\t\t    root_document.body.appendChild(root);\n\n        //compute best position\n        var left = options.left || 0;\n        var top = options.top || 0;\n        if (options.event) {\n            left = options.event.clientX - 10;\n            top = options.event.clientY - 10;\n            if (options.title) {\n                top -= 20;\n            }\n\n            if (options.parentMenu) {\n                var rect = options.parentMenu.root.getBoundingClientRect();\n                left = rect.left + rect.width;\n            }\n\n            var body_rect = document.body.getBoundingClientRect();\n            var root_rect = root.getBoundingClientRect();\n\t\t\tif(body_rect.height == 0)\n\t\t\t\tconsole.error(\"document.body height is 0. That is dangerous, set html,body { height: 100%; }\");\n\n            if (body_rect.width && left > body_rect.width - root_rect.width - 10) {\n                left = body_rect.width - root_rect.width - 10;\n            }\n            if (body_rect.height && top > body_rect.height - root_rect.height - 10) {\n                top = body_rect.height - root_rect.height - 10;\n            }\n        }\n\n        root.style.left = left + \"px\";\n        root.style.top = top + \"px\";\n\n        if (options.scale) {\n            root.style.transform = \"scale(\" + options.scale + \")\";\n        }\n    }\n\n    ContextMenu.prototype.addItem = function(name, value, options) {\n        var that = this;\n        options = options || {};\n\n        var element = document.createElement(\"div\");\n        element.className = \"litemenu-entry submenu\";\n\n        var disabled = false;\n\n        if (value === null) {\n            element.classList.add(\"separator\");\n            //element.innerHTML = \"<hr/>\"\n            //continue;\n        } else {\n            element.innerHTML = value && value.title ? value.title : name;\n            element.value = value;\n\n            if (value) {\n                if (value.disabled) {\n                    disabled = true;\n                    element.classList.add(\"disabled\");\n                }\n                if (value.submenu || value.has_submenu) {\n                    element.classList.add(\"has_submenu\");\n                }\n            }\n\n            if (typeof value == \"function\") {\n                element.dataset[\"value\"] = name;\n                element.onclick_callback = value;\n            } else {\n                element.dataset[\"value\"] = value;\n            }\n\n            if (value.className) {\n                element.className += \" \" + value.className;\n            }\n        }\n\n        this.root.appendChild(element);\n        if (!disabled) {\n            element.addEventListener(\"click\", inner_onclick);\n        }\n        if (!disabled && options.autoopen) {\n\t\t\tLiteGraph.pointerListenerAdd(element,\"enter\",inner_over);\n        }\n\n        function inner_over(e) {\n            var value = this.value;\n            if (!value || !value.has_submenu) {\n                return;\n            }\n            //if it is a submenu, autoopen like the item was clicked\n            inner_onclick.call(this, e);\n        }\n\n        //menu option clicked\n        function inner_onclick(e) {\n            var value = this.value;\n            var close_parent = true;\n\n            if (that.current_submenu) {\n                that.current_submenu.close(e);\n            }\n\n            //global callback\n            if (options.callback) {\n                var r = options.callback.call(\n                    this,\n                    value,\n                    options,\n                    e,\n                    that,\n                    options.node\n                );\n                if (r === true) {\n                    close_parent = false;\n                }\n            }\n\n            //special cases\n            if (value) {\n                if (\n                    value.callback &&\n                    !options.ignore_item_callbacks &&\n                    value.disabled !== true\n                ) {\n                    //item callback\n                    var r = value.callback.call(\n                        this,\n                        value,\n                        options,\n                        e,\n                        that,\n                        options.extra\n                    );\n                    if (r === true) {\n                        close_parent = false;\n                    }\n                }\n                if (value.submenu) {\n                    if (!value.submenu.options) {\n                        throw \"ContextMenu submenu needs options\";\n                    }\n                    var submenu = new that.constructor(value.submenu.options, {\n                        callback: value.submenu.callback,\n                        event: e,\n                        parentMenu: that,\n                        ignore_item_callbacks:\n                            value.submenu.ignore_item_callbacks,\n                        title: value.submenu.title,\n                        extra: value.submenu.extra,\n                        autoopen: options.autoopen\n                    });\n                    close_parent = false;\n                }\n            }\n\n            if (close_parent && !that.lock) {\n                that.close();\n            }\n        }\n\n        return element;\n    };\n\n    ContextMenu.prototype.close = function(e, ignore_parent_menu) {\n        if (this.root.parentNode) {\n            this.root.parentNode.removeChild(this.root);\n        }\n        if (this.parentMenu && !ignore_parent_menu) {\n            this.parentMenu.lock = false;\n            this.parentMenu.current_submenu = null;\n            if (e === undefined) {\n                this.parentMenu.close();\n            } else if (\n                e &&\n                !ContextMenu.isCursorOverElement(e, this.parentMenu.root)\n            ) {\n                ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method+\"leave\", e);\n            }\n        }\n        if (this.current_submenu) {\n            this.current_submenu.close(e, true);\n        }\n\n        if (this.root.closing_timer) {\n            clearTimeout(this.root.closing_timer);\n        }\n        \n        // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu\n        // on key press, allow filtering/selecting the context menu elements\n    };\n\n    //this code is used to trigger events easily (used in the context menu mouseleave\n    ContextMenu.trigger = function(element, event_name, params, origin) {\n        var evt = document.createEvent(\"CustomEvent\");\n        evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail\n        evt.srcElement = origin;\n        if (element.dispatchEvent) {\n            element.dispatchEvent(evt);\n        } else if (element.__events) {\n            element.__events.dispatchEvent(evt);\n        }\n        //else nothing seems binded here so nothing to do\n        return evt;\n    };\n\n    //returns the top most menu\n    ContextMenu.prototype.getTopMenu = function() {\n        if (this.options.parentMenu) {\n            return this.options.parentMenu.getTopMenu();\n        }\n        return this;\n    };\n\n    ContextMenu.prototype.getFirstEvent = function() {\n        if (this.options.parentMenu) {\n            return this.options.parentMenu.getFirstEvent();\n        }\n        return this.options.event;\n    };\n\n    ContextMenu.isCursorOverElement = function(event, element) {\n        var left = event.clientX;\n        var top = event.clientY;\n        var rect = element.getBoundingClientRect();\n        if (!rect) {\n            return false;\n        }\n        if (\n            top > rect.top &&\n            top < rect.top + rect.height &&\n            left > rect.left &&\n            left < rect.left + rect.width\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    LiteGraph.ContextMenu = ContextMenu;\n\n    LiteGraph.closeAllContextMenus = function(ref_window) {\n        ref_window = ref_window || window;\n\n        var elements = ref_window.document.querySelectorAll(\".litecontextmenu\");\n        if (!elements.length) {\n            return;\n        }\n\n        var result = [];\n        for (var i = 0; i < elements.length; i++) {\n            result.push(elements[i]);\n        }\n\n        for (var i=0; i < result.length; i++) {\n            if (result[i].close) {\n                result[i].close();\n            } else if (result[i].parentNode) {\n                result[i].parentNode.removeChild(result[i]);\n            }\n        }\n    };\n\n    LiteGraph.extendClass = function(target, origin) {\n        for (var i in origin) {\n            //copy class properties\n            if (target.hasOwnProperty(i)) {\n                continue;\n            }\n            target[i] = origin[i];\n        }\n\n        if (origin.prototype) {\n            //copy prototype properties\n            for (var i in origin.prototype) {\n                //only enumerable\n                if (!origin.prototype.hasOwnProperty(i)) {\n                    continue;\n                }\n\n                if (target.prototype.hasOwnProperty(i)) {\n                    //avoid overwriting existing ones\n                    continue;\n                }\n\n                //copy getters\n                if (origin.prototype.__lookupGetter__(i)) {\n                    target.prototype.__defineGetter__(\n                        i,\n                        origin.prototype.__lookupGetter__(i)\n                    );\n                } else {\n                    target.prototype[i] = origin.prototype[i];\n                }\n\n                //and setters\n                if (origin.prototype.__lookupSetter__(i)) {\n                    target.prototype.__defineSetter__(\n                        i,\n                        origin.prototype.__lookupSetter__(i)\n                    );\n                }\n            }\n        }\n    };\n\n\t//used by some widgets to render a curve editor\n\tfunction CurveEditor( points )\n\t{\n\t\tthis.points = points;\n\t\tthis.selected = -1;\n\t\tthis.nearest = -1;\n\t\tthis.size = null; //stores last size used\n\t\tthis.must_update = true;\n\t\tthis.margin = 5;\n\t}\n\n\tCurveEditor.sampleCurve = function(f,points)\n\t{\n\t\tif(!points)\n\t\t\treturn;\n\t\tfor(var i = 0; i < points.length - 1; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tvar pn = points[i+1];\n\t\t\tif(pn[0] < f)\n\t\t\t\tcontinue;\n\t\t\tvar r = (pn[0] - p[0]);\n\t\t\tif( Math.abs(r) < 0.00001 )\n\t\t\t\treturn p[1];\n\t\t\tvar local_f = (f - p[0]) / r;\n\t\t\treturn p[1] * (1.0 - local_f) + pn[1] * local_f;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tCurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tthis.size = size;\n\t\tvar w = size[0] - this.margin * 2;\n\t\tvar h = size[1] - this.margin * 2;\n\n\t\tline_color = line_color || \"#666\";\n\n\t\tctx.save();\n\t\tctx.translate(this.margin,this.margin);\n\n\t\tif(background_color)\n\t\t{\n\t\t\tctx.fillStyle = \"#111\";\n\t\t\tctx.fillRect(0,0,w,h);\n\t\t\tctx.fillStyle = \"#222\";\n\t\t\tctx.fillRect(w*0.5,0,1,h);\n\t\t\tctx.strokeStyle = \"#333\";\n\t\t\tctx.strokeRect(0,0,w,h);\n\t\t}\n\t\tctx.strokeStyle = line_color;\n\t\tif(inactive)\n\t\t\tctx.globalAlpha = 0.5;\n\t\tctx.beginPath();\n\t\tfor(var i = 0; i < points.length; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tctx.lineTo( p[0] * w, (1.0 - p[1]) * h );\n\t\t}\n\t\tctx.stroke();\n\t\tctx.globalAlpha = 1;\n\t\tif(!inactive)\n\t\t\tfor(var i = 0; i < points.length; ++i)\n\t\t\t{\n\t\t\t\tvar p = points[i];\n\t\t\t\tctx.fillStyle = this.selected == i ? \"#FFF\" : (this.nearest == i ? \"#DDD\" : \"#AAA\");\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 );\n\t\t\t\tctx.fill();\n\t\t\t}\n\t\tctx.restore();\n\t}\n\n\t//localpos is mouse in curve editor space\n\tCurveEditor.prototype.onMouseDown = function( localpos, graphcanvas )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tif( localpos[1] < 0 )\n\t\t\treturn;\n\n\t\t//this.captureInput(true);\n\t\tvar w = this.size[0] - this.margin * 2;\n\t\tvar h = this.size[1] - this.margin * 2;\n\t\tvar x = localpos[0] - this.margin;\n\t\tvar y = localpos[1] - this.margin;\n\t\tvar pos = [x,y];\n\t\tvar max_dist = 30 / graphcanvas.ds.scale;\n\t\t//search closer one\n\t\tthis.selected = this.getCloserPoint(pos, max_dist);\n\t\t//create one\n\t\tif(this.selected == -1)\n\t\t{\n\t\t\tvar point = [x / w, 1 - y / h];\n\t\t\tpoints.push(point);\n\t\t\tpoints.sort(function(a,b){ return a[0] - b[0]; });\n\t\t\tthis.selected = points.indexOf(point);\n\t\t\tthis.must_update = true;\n\t\t}\n\t\tif(this.selected != -1)\n\t\t\treturn true;\n\t}\n\n\tCurveEditor.prototype.onMouseMove = function( localpos, graphcanvas )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tvar s = this.selected;\n\t\tif(s < 0)\n\t\t\treturn;\n\t\tvar x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 );\n\t\tvar y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 );\n\t\tvar curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)];\n\t\tvar max_dist = 30 / graphcanvas.ds.scale;\n\t\tthis._nearest = this.getCloserPoint(curvepos, max_dist);\n\t\tvar point = points[s];\n\t\tif(point)\n\t\t{\n\t\t\tvar is_edge_point = s == 0 || s == points.length - 1;\n\t\t\tif( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) )\n\t\t\t{\n\t\t\t\tpoints.splice(s,1);\n\t\t\t\tthis.selected = -1;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif( !is_edge_point ) //not edges\n\t\t\t\tpoint[0] = clamp(x, 0, 1);\n\t\t\telse\n\t\t\t\tpoint[0] = s == 0 ? 0 : 1;\n\t\t\tpoint[1] = 1.0 - clamp(y, 0, 1);\n\t\t\tpoints.sort(function(a,b){ return a[0] - b[0]; });\n\t\t\tthis.selected = points.indexOf(point);\n\t\t\tthis.must_update = true;\n\t\t}\n\t}\n\n\tCurveEditor.prototype.onMouseUp = function( localpos, graphcanvas )\n\t{\n\t\tthis.selected = -1;\n\t\treturn false;\n\t}\n\n\tCurveEditor.prototype.getCloserPoint = function(pos, max_dist)\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn -1;\n\t\tmax_dist = max_dist || 30;\n\t\tvar w = (this.size[0] - this.margin * 2);\n\t\tvar h = (this.size[1] - this.margin * 2);\n\t\tvar num = points.length;\n\t\tvar p2 = [0,0];\n\t\tvar min_dist = 1000000;\n\t\tvar closest = -1;\n\t\tvar last_valid = -1;\n\t\tfor(var i = 0; i < num; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tp2[0] = p[0] * w;\n\t\t\tp2[1] = (1.0 - p[1]) * h;\n\t\t\tif(p2[0] < pos[0])\n\t\t\t\tlast_valid = i;\n\t\t\tvar dist = vec2.distance(pos,p2);\n\t\t\tif(dist > min_dist || dist > max_dist)\n\t\t\t\tcontinue;\n\t\t\tclosest = i;\n\t\t\tmin_dist = dist;\n\t\t}\n\t\treturn closest;\n\t}\n\n\tLiteGraph.CurveEditor = CurveEditor;\n\n    //used to create nodes from wrapping functions\n    LiteGraph.getParameterNames = function(func) {\n        return (func + \"\")\n            .replace(/[/][/].*$/gm, \"\") // strip single-line comments\n            .replace(/\\s+/g, \"\") // strip white space\n            .replace(/[/][*][^/*]*[*][/]/g, \"\") // strip multi-line comments  /**/\n            .split(\"){\", 1)[0]\n            .replace(/^[^(]*[(]/, \"\") // extract the parameters\n            .replace(/=[^,]+/g, \"\") // strip any ES6 defaults\n            .split(\",\")\n            .filter(Boolean); // split & filter [\"\"]\n    };\n\n\t/* helper for interaction: pointer, touch, mouse Listeners\n\tused by LGraphCanvas DragAndScale ContextMenu*/\n\tLiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) {\n\t\tif (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!==\"function\"){\n\t\t\t//console.log(\"cant pointerListenerAdd \"+oDOM+\", \"+sEvent+\", \"+fCall);\n\t\t\treturn; // -- break --\n\t\t}\n\t\t\n\t\tvar sMethod = LiteGraph.pointerevents_method;\n\t\tvar sEvent = sEvIn;\n\t\t\n\t\t// UNDER CONSTRUCTION\n\t\t// convert pointerevents to touch event when not available\n\t\tif (sMethod==\"pointer\" && !window.PointerEvent){ \n\t\t\tconsole.warn(\"sMethod=='pointer' && !window.PointerEvent\");\n\t\t\tconsole.log(\"Converting pointer[\"+sEvent+\"] : down move up cancel enter TO touchstart touchmove touchend, etc ..\");\n\t\t\tswitch(sEvent){\n\t\t\t\tcase \"down\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\tsEvent = \"start\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"move\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\t//sEvent = \"move\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"up\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\tsEvent = \"end\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"cancel\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\t//sEvent = \"cancel\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"enter\":{\n\t\t\t\t\tconsole.log(\"debug: Should I send a move event?\"); // ???\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// case \"over\": case \"out\": not used at now\n\t\t\t\tdefault:{\n\t\t\t\t\tconsole.warn(\"PointerEvent not available in this browser ? The event \"+sEvent+\" would not be called\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tswitch(sEvent){\n\t\t\t//both pointer and move events\n\t\t\tcase \"down\": case \"up\": case \"move\": case \"over\": case \"out\": case \"enter\":\n\t\t\t{\n\t\t\t\toDOM.addEventListener(sMethod+sEvent, fCall, capture);\n\t\t\t}\n\t\t\t// only pointerevents\n\t\t\tcase \"leave\": case \"cancel\": case \"gotpointercapture\": case \"lostpointercapture\":\n\t\t\t{\n\t\t\t\tif (sMethod!=\"mouse\"){\n\t\t\t\t\treturn oDOM.addEventListener(sMethod+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// not \"pointer\" || \"mouse\"\n\t\t\tdefault:\n\t\t\t\treturn oDOM.addEventListener(sEvent, fCall, capture);\n\t\t}\n\t}\n\tLiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) {\n\t\tif (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!==\"function\"){\n\t\t\t//console.log(\"cant pointerListenerRemove \"+oDOM+\", \"+sEvent+\", \"+fCall);\n\t\t\treturn; // -- break --\n\t\t}\n\t\tswitch(sEvent){\n\t\t\t//both pointer and move events\n\t\t\tcase \"down\": case \"up\": case \"move\": case \"over\": case \"out\": case \"enter\":\n\t\t\t{\n\t\t\t\tif (LiteGraph.pointerevents_method==\"pointer\" || LiteGraph.pointerevents_method==\"mouse\"){\n\t\t\t\t\toDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// only pointerevents\n\t\t\tcase \"leave\": case \"cancel\": case \"gotpointercapture\": case \"lostpointercapture\":\n\t\t\t{\n\t\t\t\tif (LiteGraph.pointerevents_method==\"pointer\"){\n\t\t\t\t\treturn oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// not \"pointer\" || \"mouse\"\n\t\t\tdefault:\n\t\t\t\treturn oDOM.removeEventListener(sEvent, fCall, capture);\n\t\t}\n\t}\n\n    function clamp(v, a, b) {\n        return a > v ? a : b < v ? b : v;\n    };\n    global.clamp = clamp;\n\n    if (typeof window != \"undefined\" && !window[\"requestAnimationFrame\"]) {\n        window.requestAnimationFrame =\n            window.webkitRequestAnimationFrame ||\n            window.mozRequestAnimationFrame ||\n            function(callback) {\n                window.setTimeout(callback, 1000 / 60);\n            };\n    }\n})(this);\n\nif (typeof exports != \"undefined\") {\n    exports.LiteGraph = this.LiteGraph;\n    exports.LGraph = this.LGraph;\n    exports.LLink = this.LLink;\n    exports.LGraphNode = this.LGraphNode;\n    exports.LGraphGroup = this.LGraphGroup;\n    exports.DragAndScale = this.DragAndScale;\n    exports.LGraphCanvas = this.LGraphCanvas;\n    exports.ContextMenu = this.ContextMenu;\n}\n\n\n//basic nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    //Constant\r\n    function Time() {\r\n        this.addOutput(\"in ms\", \"number\");\r\n        this.addOutput(\"in sec\", \"number\");\r\n    }\r\n\r\n    Time.title = \"Time\";\r\n    Time.desc = \"Time\";\r\n\r\n    Time.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.graph.globaltime * 1000);\r\n        this.setOutputData(1, this.graph.globaltime);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/time\", Time);\r\n\r\n    //Subgraph: a node that contains a graph\r\n    function Subgraph() {\r\n        var that = this;\r\n        this.size = [140, 80];\r\n        this.properties = { enabled: true };\r\n        this.enabled = true;\r\n\r\n        //create inner graph\r\n        this.subgraph = new LiteGraph.LGraph();\r\n        this.subgraph._subgraph_node = this;\r\n        this.subgraph._is_subgraph = true;\r\n\r\n        this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this);\r\n\r\n\t\t//nodes input node added inside\r\n        this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this);\r\n        this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this);\r\n        this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(this);\r\n        this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this);\r\n\r\n        this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this);\r\n        this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this);\r\n        this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(this);\r\n        this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this);\r\n    }\r\n\r\n    Subgraph.title = \"Subgraph\";\r\n    Subgraph.desc = \"Graph inside a node\";\r\n    Subgraph.title_color = \"#334\";\r\n\r\n    Subgraph.prototype.onGetInputs = function() {\r\n        return [[\"enabled\", \"boolean\"]];\r\n    };\r\n\r\n\t/*\r\n    Subgraph.prototype.onDrawTitle = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        ctx.fillStyle = \"#555\";\r\n        var w = LiteGraph.NODE_TITLE_HEIGHT;\r\n        var x = this.size[0] - w;\r\n        ctx.fillRect(x, -w, w, w);\r\n        ctx.fillStyle = \"#333\";\r\n        ctx.beginPath();\r\n        ctx.moveTo(x + w * 0.2, -w * 0.6);\r\n        ctx.lineTo(x + w * 0.8, -w * 0.6);\r\n        ctx.lineTo(x + w * 0.5, -w * 0.3);\r\n        ctx.fill();\r\n    };\r\n\t*/\r\n\r\n    Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) {\r\n        var that = this;\r\n        setTimeout(function() {\r\n            graphcanvas.openSubgraph(that.subgraph);\r\n        }, 10);\r\n    };\r\n\r\n\t/*\r\n    Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) {\r\n        if (\r\n            !this.flags.collapsed &&\r\n            pos[0] > this.size[0] - LiteGraph.NODE_TITLE_HEIGHT &&\r\n            pos[1] < 0\r\n        ) {\r\n            var that = this;\r\n            setTimeout(function() {\r\n                graphcanvas.openSubgraph(that.subgraph);\r\n            }, 10);\r\n        }\r\n    };\r\n\t*/\r\n\r\n    Subgraph.prototype.onAction = function(action, param) {\r\n        this.subgraph.onAction(action, param);\r\n    };\r\n\r\n    Subgraph.prototype.onExecute = function() {\r\n        this.enabled = this.getInputOrProperty(\"enabled\");\r\n        if (!this.enabled) {\r\n            return;\r\n        }\r\n\r\n        //send inputs to subgraph global inputs\r\n        if (this.inputs) {\r\n            for (var i = 0; i < this.inputs.length; i++) {\r\n                var input = this.inputs[i];\r\n                var value = this.getInputData(i);\r\n                this.subgraph.setInputData(input.name, value);\r\n            }\r\n        }\r\n\r\n        //execute\r\n        this.subgraph.runStep();\r\n\r\n        //send subgraph global outputs to outputs\r\n        if (this.outputs) {\r\n            for (var i = 0; i < this.outputs.length; i++) {\r\n                var output = this.outputs[i];\r\n                var value = this.subgraph.getOutputData(output.name);\r\n                this.setOutputData(i, value);\r\n            }\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.sendEventToAllNodes = function(eventname, param, mode) {\r\n        if (this.enabled) {\r\n            this.subgraph.sendEventToAllNodes(eventname, param, mode);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onDrawBackground = function (ctx, graphcanvas, canvas, pos) {\r\n        if (this.flags.collapsed)\r\n            return;\r\n        var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n        // button\r\n        var over = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0], LiteGraph.NODE_TITLE_HEIGHT);\r\n        let overleft = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0] / 2, LiteGraph.NODE_TITLE_HEIGHT)\r\n        ctx.fillStyle = over ? \"#555\" : \"#222\";\r\n        ctx.beginPath();\r\n        if (this._shape == LiteGraph.BOX_SHAPE) {\r\n            if (overleft) {\r\n                ctx.rect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT);\r\n            } else {\r\n                ctx.rect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT);\r\n            }\r\n        }\r\n        else {\r\n            if (overleft) {\r\n                ctx.roundRect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]);\r\n            } else {\r\n                ctx.roundRect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]);\r\n            }\r\n        }\r\n        if (over) {\r\n            ctx.fill();\r\n        } else {\r\n            ctx.fillRect(0, y, this.size[0] + 1, LiteGraph.NODE_TITLE_HEIGHT);\r\n        }\r\n        // button\r\n        ctx.textAlign = \"center\";\r\n        ctx.font = \"24px Arial\";\r\n        ctx.fillStyle = over ? \"#DDD\" : \"#999\";\r\n        ctx.fillText(\"+\", this.size[0] * 0.25, y + 24);\r\n        ctx.fillText(\"+\", this.size[0] * 0.75, y + 24);\r\n    }\r\n\r\n    // Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas)\r\n    // {\r\n    // \tvar y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n    // \tif(localpos[1] > y)\r\n    // \t{\r\n    // \t\tgraphcanvas.showSubgraphPropertiesDialog(this);\r\n    // \t}\r\n    // }\r\n    Subgraph.prototype.onMouseDown = function (e, localpos, graphcanvas) {\r\n        var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n        console.log(0)\r\n        if (localpos[1] > y) {\r\n            if (localpos[0] < this.size[0] / 2) {\r\n                console.log(1)\r\n                graphcanvas.showSubgraphPropertiesDialog(this);\r\n            } else {\r\n                console.log(2)\r\n                graphcanvas.showSubgraphPropertiesDialogRight(this);\r\n            }\r\n        }\r\n    }\r\n\tSubgraph.prototype.computeSize = function()\r\n\t{\r\n\t\tvar num_inputs = this.inputs ? this.inputs.length : 0;\r\n\t\tvar num_outputs = this.outputs ? this.outputs.length : 0;\r\n\t\treturn [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ];\r\n\t}\r\n\r\n    //**** INPUTS ***********************************\r\n    Subgraph.prototype.onSubgraphTrigger = function(event, param) {\r\n        var slot = this.findOutputSlot(event);\r\n        if (slot != -1) {\r\n            this.triggerSlot(slot);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphNewInput = function(name, type) {\r\n        var slot = this.findInputSlot(name);\r\n        if (slot == -1) {\r\n            //add input to the node\r\n            this.addInput(name, type);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRenamedInput = function(oldname, name) {\r\n        var slot = this.findInputSlot(oldname);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getInputInfo(slot);\r\n        info.name = name;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphTypeChangeInput = function(name, type) {\r\n        var slot = this.findInputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getInputInfo(slot);\r\n        info.type = type;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRemovedInput = function(name) {\r\n        var slot = this.findInputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        this.removeInput(slot);\r\n    };\r\n\r\n    //**** OUTPUTS ***********************************\r\n    Subgraph.prototype.onSubgraphNewOutput = function(name, type) {\r\n        var slot = this.findOutputSlot(name);\r\n        if (slot == -1) {\r\n            this.addOutput(name, type);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRenamedOutput = function(oldname, name) {\r\n        var slot = this.findOutputSlot(oldname);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getOutputInfo(slot);\r\n        info.name = name;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphTypeChangeOutput = function(name, type) {\r\n        var slot = this.findOutputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getOutputInfo(slot);\r\n        info.type = type;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRemovedOutput = function(name) {\r\n        var slot = this.findOutputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        this.removeOutput(slot);\r\n    };\r\n    // *****************************************************\r\n\r\n    Subgraph.prototype.getExtraMenuOptions = function(graphcanvas) {\r\n        var that = this;\r\n        return [\r\n            {\r\n                content: \"Open\",\r\n                callback: function() {\r\n                    graphcanvas.openSubgraph(that.subgraph);\r\n                }\r\n            }\r\n        ];\r\n    };\r\n\r\n    Subgraph.prototype.onResize = function(size) {\r\n        size[1] += 20;\r\n    };\r\n\r\n    Subgraph.prototype.serialize = function() {\r\n        var data = LiteGraph.LGraphNode.prototype.serialize.call(this);\r\n        data.subgraph = this.subgraph.serialize();\r\n        return data;\r\n    };\r\n    //no need to define node.configure, the default method detects node.subgraph and passes the object to node.subgraph.configure()\r\n\r\n    Subgraph.prototype.reassignSubgraphUUIDs = function(graph) {\r\n        const idMap = { nodeIDs: {}, linkIDs: {} }\r\n\r\n        for (const node of graph.nodes) {\r\n            const oldID = node.id\r\n            const newID = LiteGraph.uuidv4()\r\n            node.id = newID\r\n\r\n            if (idMap.nodeIDs[oldID] || idMap.nodeIDs[newID]) {\r\n                throw new Error(`New/old node UUID wasn't unique in changed map! ${oldID} ${newID}`)\r\n            }\r\n\r\n            idMap.nodeIDs[oldID] = newID\r\n            idMap.nodeIDs[newID] = oldID\r\n        }\r\n\r\n        for (const link of graph.links) {\r\n            const oldID = link[0]\r\n            const newID = LiteGraph.uuidv4();\r\n            link[0] = newID\r\n\r\n            if (idMap.linkIDs[oldID] || idMap.linkIDs[newID]) {\r\n                throw new Error(`New/old link UUID wasn't unique in changed map! ${oldID} ${newID}`)\r\n            }\r\n\r\n            idMap.linkIDs[oldID] = newID\r\n            idMap.linkIDs[newID] = oldID\r\n\r\n            const nodeFrom = link[1]\r\n            const nodeTo = link[3]\r\n\r\n            if (!idMap.nodeIDs[nodeFrom]) {\r\n                throw new Error(`Old node UUID not found in mapping! ${nodeFrom}`)\r\n            }\r\n\r\n            link[1] = idMap.nodeIDs[nodeFrom]\r\n\r\n            if (!idMap.nodeIDs[nodeTo]) {\r\n                throw new Error(`Old node UUID not found in mapping! ${nodeTo}`)\r\n            }\r\n\r\n            link[3] = idMap.nodeIDs[nodeTo]\r\n        }\r\n\r\n        // Reconnect links\r\n        for (const node of graph.nodes) {\r\n            if (node.inputs) {\r\n                for (const input of node.inputs) {\r\n                    if (input.link) {\r\n                        input.link = idMap.linkIDs[input.link]\r\n                    }\r\n                }\r\n            }\r\n            if (node.outputs) {\r\n                for (const output of node.outputs) {\r\n                    if (output.links) {\r\n                        output.links = output.links.map(l => idMap.linkIDs[l]);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        // Recurse!\r\n        for (const node of graph.nodes) {\r\n            if (node.type === \"graph/subgraph\") {\r\n                const merge = reassignGraphUUIDs(node.subgraph);\r\n                idMap.nodeIDs.assign(merge.nodeIDs)\r\n                idMap.linkIDs.assign(merge.linkIDs)\r\n            }\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.clone = function() {\r\n        var node = LiteGraph.createNode(this.type);\r\n        var data = this.serialize();\r\n\r\n        if (LiteGraph.use_uuids) {\r\n            // LGraph.serialize() seems to reuse objects in the original graph. But we\r\n            // need to change node IDs here, so clone it first.\r\n            const subgraph = LiteGraph.cloneObject(data.subgraph)\r\n\r\n            this.reassignSubgraphUUIDs(subgraph);\r\n\r\n            data.subgraph = subgraph;\r\n        }\r\n\r\n        delete data[\"id\"];\r\n        delete data[\"inputs\"];\r\n        delete data[\"outputs\"];\r\n        node.configure(data);\r\n        return node;\r\n    };\r\n\r\n\tSubgraph.prototype.buildFromNodes = function(nodes)\r\n\t{\r\n\t\t//clear all?\r\n\t\t//TODO\r\n\r\n\t\t//nodes that connect data between parent graph and subgraph\r\n\t\tvar subgraph_inputs = [];\r\n\t\tvar subgraph_outputs = [];\r\n\r\n\t\t//mark inner nodes\r\n\t\tvar ids = {};\r\n\t\tvar min_x = 0;\r\n\t\tvar max_x = 0;\r\n\t\tfor(var i = 0; i < nodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar node = nodes[i];\r\n\t\t\tids[ node.id ] = node;\r\n\t\t\tmin_x = Math.min( node.pos[0], min_x );\r\n\t\t\tmax_x = Math.max( node.pos[0], min_x );\r\n\t\t}\r\n\t\t\r\n\t\tvar last_input_y = 0;\r\n\t\tvar last_output_y = 0;\r\n\r\n\t\tfor(var i = 0; i < nodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar node = nodes[i];\r\n\t\t\t//check inputs\r\n\t\t\tif( node.inputs )\r\n\t\t\t\tfor(var j = 0; j < node.inputs.length; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar input = node.inputs[j];\r\n\t\t\t\t\tif( !input || !input.link )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tvar link = node.graph.links[ input.link ];\r\n\t\t\t\t\tif(!link)\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tif( ids[ link.origin_id ] )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t//this.addInput(input.name,link.type);\r\n\t\t\t\t\tthis.subgraph.addInput(input.name,link.type);\r\n\t\t\t\t\t/*\r\n\t\t\t\t\tvar input_node = LiteGraph.createNode(\"graph/input\");\r\n\t\t\t\t\tthis.subgraph.add( input_node );\r\n\t\t\t\t\tinput_node.pos = [min_x - 200, last_input_y ];\r\n\t\t\t\t\tlast_input_y += 100;\r\n\t\t\t\t\t*/\r\n\t\t\t\t}\r\n\r\n\t\t\t//check outputs\r\n\t\t\tif( node.outputs )\r\n\t\t\t\tfor(var j = 0; j < node.outputs.length; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar output = node.outputs[j];\r\n\t\t\t\t\tif( !output || !output.links || !output.links.length )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tvar is_external = false;\r\n\t\t\t\t\tfor(var k = 0; k < output.links.length; ++k)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar link = node.graph.links[ output.links[k] ];\r\n\t\t\t\t\t\tif(!link)\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\tif( ids[ link.target_id ] )\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\tis_external = true;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif(!is_external)\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t//this.addOutput(output.name,output.type);\r\n\t\t\t\t\t/*\r\n\t\t\t\t\tvar output_node = LiteGraph.createNode(\"graph/output\");\r\n\t\t\t\t\tthis.subgraph.add( output_node );\r\n\t\t\t\t\toutput_node.pos = [max_x + 50, last_output_y ];\r\n\t\t\t\t\tlast_output_y += 100;\r\n\t\t\t\t\t*/\r\n\t\t\t\t}\r\n\t\t}\r\n\r\n\t\t//detect inputs and outputs\r\n\t\t\t//split every connection in two data_connection nodes\r\n\t\t\t//keep track of internal connections\r\n\t\t\t//connect external connections\r\n\r\n\t\t//clone nodes inside subgraph and try to reconnect them\r\n\r\n\t\t//connect edge subgraph nodes to extarnal connections nodes\r\n\t}\r\n\r\n    LiteGraph.Subgraph = Subgraph;\r\n    LiteGraph.registerNodeType(\"graph/subgraph\", Subgraph);\r\n\r\n    //Input for a subgraph\r\n    function GraphInput() {\r\n        this.addOutput(\"\", \"number\");\r\n\r\n        this.name_in_graph = \"\";\r\n        this.properties = {\r\n\t\t\tname: \"\",\r\n\t\t\ttype: \"number\",\r\n\t\t\tvalue: 0\r\n\t\t}; \r\n\r\n        var that = this;\r\n\r\n        this.name_widget = this.addWidget(\r\n            \"text\",\r\n            \"Name\",\r\n            this.properties.name,\r\n            function(v) {\r\n                if (!v) {\r\n                    return;\r\n                }\r\n                that.setProperty(\"name\",v);\r\n            }\r\n        );\r\n        this.type_widget = this.addWidget(\r\n            \"text\",\r\n            \"Type\",\r\n            this.properties.type,\r\n            function(v) {\r\n\t\t\t\tthat.setProperty(\"type\",v);\r\n            }\r\n        );\r\n\r\n        this.value_widget = this.addWidget(\r\n            \"number\",\r\n            \"Value\",\r\n            this.properties.value,\r\n            function(v) {\r\n                that.setProperty(\"value\",v);\r\n            }\r\n        );\r\n\r\n        this.widgets_up = true;\r\n        this.size = [180, 90];\r\n    }\r\n\r\n    GraphInput.title = \"Input\";\r\n    GraphInput.desc = \"Input of the graph\";\r\n\r\n\tGraphInput.prototype.onConfigure = function()\r\n\r\n\t{\r\n\t\tthis.updateType();\r\n\t}\r\n\r\n\t//ensures the type in the node output and the type in the associated graph input are the same\r\n\tGraphInput.prototype.updateType = function()\r\n\t{\r\n\t\tvar type = this.properties.type;\r\n\t\tthis.type_widget.value = type;\r\n\r\n\t\t//update output\r\n\t\tif(this.outputs[0].type != type)\r\n\t\t{\r\n\t        if (!LiteGraph.isValidConnection(this.outputs[0].type,type))\r\n\t\t\t\tthis.disconnectOutput(0);\r\n\t\t\tthis.outputs[0].type = type;\r\n\t\t}\r\n\r\n\t\t//update widget\r\n\t\tif(type == \"number\")\r\n\t\t{\r\n\t\t\tthis.value_widget.type = \"number\";\r\n\t\t\tthis.value_widget.value = 0;\r\n\t\t}\r\n\t\telse if(type == \"boolean\")\r\n\t\t{\r\n\t\t\tthis.value_widget.type = \"toggle\";\r\n\t\t\tthis.value_widget.value = true;\r\n\t\t}\r\n\t\telse if(type == \"string\")\r\n\t\t{\r\n\t\t\tthis.value_widget.type = \"text\";\r\n\t\t\tthis.value_widget.value = \"\";\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.value_widget.type = null;\r\n\t\t\tthis.value_widget.value = null;\r\n\t\t}\r\n\t\tthis.properties.value = this.value_widget.value;\r\n\r\n\t\t//update graph\r\n\t\tif (this.graph && this.name_in_graph) {\r\n\t\t\tthis.graph.changeInputType(this.name_in_graph, type);\r\n\t\t}\r\n\t}\r\n\r\n\t//this is executed AFTER the property has changed\r\n\tGraphInput.prototype.onPropertyChanged = function(name,v)\r\n\t{\r\n\t\tif( name == \"name\" )\r\n\t\t{\r\n\t\t\tif (v == \"\" || v == this.name_in_graph || v == \"enabled\") {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tif(this.graph)\r\n\t\t\t{\r\n\t\t\t\tif (this.name_in_graph) {\r\n\t\t\t\t\t//already added\r\n\t\t\t\t\tthis.graph.renameInput( this.name_in_graph, v );\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.graph.addInput( v, this.properties.type );\r\n\t\t\t\t}\r\n\t\t\t} //what if not?!\r\n\t\t\tthis.name_widget.value = v;\r\n\t\t\tthis.name_in_graph = v;\r\n\t\t}\r\n\t\telse if( name == \"type\" )\r\n\t\t{\r\n\t\t\tthis.updateType();\r\n\t\t}\r\n\t\telse if( name == \"value\" )\r\n\t\t{\r\n\t\t}\r\n\t}\r\n\r\n    GraphInput.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.name;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    GraphInput.prototype.onAction = function(action, param) {\r\n        if (this.properties.type == LiteGraph.EVENT) {\r\n            this.triggerSlot(0, param);\r\n        }\r\n    };\r\n\r\n    GraphInput.prototype.onExecute = function() {\r\n        var name = this.properties.name;\r\n        //read from global input\r\n        var data = this.graph.inputs[name];\r\n        if (!data) {\r\n            this.setOutputData(0, this.properties.value );\r\n\t\t\treturn;\r\n        }\r\n\r\n        this.setOutputData(0, data.value !== undefined ? data.value : this.properties.value );\r\n    };\r\n\r\n    GraphInput.prototype.onRemoved = function() {\r\n        if (this.name_in_graph) {\r\n            this.graph.removeInput(this.name_in_graph);\r\n        }\r\n    };\r\n\r\n    LiteGraph.GraphInput = GraphInput;\r\n    LiteGraph.registerNodeType(\"graph/input\", GraphInput);\r\n\r\n    //Output for a subgraph\r\n    function GraphOutput() {\r\n        this.addInput(\"\", \"\");\r\n\r\n        this.name_in_graph = \"\";\r\n        this.properties = { name: \"\", type: \"\" };\r\n        var that = this;\r\n\r\n        // Object.defineProperty(this.properties, \"name\", {\r\n        //     get: function() {\r\n        //         return that.name_in_graph;\r\n        //     },\r\n        //     set: function(v) {\r\n        //         if (v == \"\" || v == that.name_in_graph) {\r\n        //             return;\r\n        //         }\r\n        //         if (that.name_in_graph) {\r\n        //             //already added\r\n        //             that.graph.renameOutput(that.name_in_graph, v);\r\n        //         } else {\r\n        //             that.graph.addOutput(v, that.properties.type);\r\n        //         }\r\n        //         that.name_widget.value = v;\r\n        //         that.name_in_graph = v;\r\n        //     },\r\n        //     enumerable: true\r\n        // });\r\n\r\n        // Object.defineProperty(this.properties, \"type\", {\r\n        //     get: function() {\r\n        //         return that.inputs[0].type;\r\n        //     },\r\n        //     set: function(v) {\r\n        //         if (v == \"action\" || v == \"event\") {\r\n        //             v = LiteGraph.ACTION;\r\n        //         }\r\n\t\t//         if (!LiteGraph.isValidConnection(that.inputs[0].type,v))\r\n\t\t// \t\t\tthat.disconnectInput(0);\r\n        //         that.inputs[0].type = v;\r\n        //         if (that.name_in_graph) {\r\n        //             //already added\r\n        //             that.graph.changeOutputType(\r\n        //                 that.name_in_graph,\r\n        //                 that.inputs[0].type\r\n        //             );\r\n        //         }\r\n        //         that.type_widget.value = v || \"\";\r\n        //     },\r\n        //     enumerable: true\r\n        // });\r\n\r\n        this.name_widget = this.addWidget(\"text\",\"Name\",this.properties.name,\"name\");\r\n        this.type_widget = this.addWidget(\"text\",\"Type\",this.properties.type,\"type\");\r\n        this.widgets_up = true;\r\n        this.size = [180, 60];\r\n    }\r\n\r\n    GraphOutput.title = \"Output\";\r\n    GraphOutput.desc = \"Output of the graph\";\r\n\r\n    GraphOutput.prototype.onPropertyChanged = function (name, v) {\r\n        if (name == \"name\") {\r\n            if (v == \"\" || v == this.name_in_graph || v == \"enabled\") {\r\n                return false;\r\n            }\r\n            if (this.graph) {\r\n                if (this.name_in_graph) {\r\n                    //already added\r\n                    this.graph.renameOutput(this.name_in_graph, v);\r\n                } else {\r\n                    this.graph.addOutput(v, this.properties.type);\r\n                }\r\n            } //what if not?!\r\n            this.name_widget.value = v;\r\n            this.name_in_graph = v;\r\n        }\r\n        else if (name == \"type\") {\r\n            this.updateType();\r\n        }\r\n        else if (name == \"value\") {\r\n        }\r\n    }\r\n     \r\n    GraphOutput.prototype.updateType = function () {\r\n        var type = this.properties.type;\r\n        if (this.type_widget)\r\n            this.type_widget.value = type;\r\n\r\n        //update output\r\n        if (this.inputs[0].type != type) {\r\n\r\n\t\t\tif ( type == \"action\" || type == \"event\")\r\n\t            type = LiteGraph.EVENT;\r\n\t\t\tif (!LiteGraph.isValidConnection(this.inputs[0].type, type))\r\n\t\t\t\tthis.disconnectInput(0);\r\n\t\t\tthis.inputs[0].type = type;\r\n        }\r\n\r\n        //update graph\r\n        if (this.graph && this.name_in_graph) {\r\n            this.graph.changeOutputType(this.name_in_graph, type);\r\n        }\r\n    }\r\n\r\n\r\n\r\n    GraphOutput.prototype.onExecute = function() {\r\n        this._value = this.getInputData(0);\r\n        this.graph.setOutputData(this.properties.name, this._value);\r\n    };\r\n\r\n    GraphOutput.prototype.onAction = function(action, param) {\r\n        if (this.properties.type == LiteGraph.ACTION) {\r\n            this.graph.trigger( this.properties.name, param );\r\n        }\r\n    };\r\n\r\n    GraphOutput.prototype.onRemoved = function() {\r\n        if (this.name_in_graph) {\r\n            this.graph.removeOutput(this.name_in_graph);\r\n        }\r\n    };\r\n\r\n    GraphOutput.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.name;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    LiteGraph.GraphOutput = GraphOutput;\r\n    LiteGraph.registerNodeType(\"graph/output\", GraphOutput);\r\n\r\n    //Constant\r\n    function ConstantNumber() {\r\n        this.addOutput(\"value\", \"number\");\r\n        this.addProperty(\"value\", 1.0);\r\n        this.widget = this.addWidget(\"number\",\"value\",1,\"value\");\r\n        this.widgets_up = true;\r\n        this.size = [180, 30];\r\n    }\r\n\r\n    ConstantNumber.title = \"Const Number\";\r\n    ConstantNumber.desc = \"Constant number\";\r\n\r\n    ConstantNumber.prototype.onExecute = function() {\r\n        this.setOutputData(0, parseFloat(this.properties[\"value\"]));\r\n    };\r\n\r\n    ConstantNumber.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.value;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n\tConstantNumber.prototype.setValue = function(v)\r\n\t{\r\n\t\tthis.setProperty(\"value\",v);\r\n\t}\r\n\r\n    ConstantNumber.prototype.onDrawBackground = function(ctx) {\r\n        //show the current value\r\n        this.outputs[0].label = this.properties[\"value\"].toFixed(3);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/const\", ConstantNumber);\r\n\r\n    function ConstantBoolean() {\r\n        this.addOutput(\"bool\", \"boolean\");\r\n        this.addProperty(\"value\", true);\r\n        this.widget = this.addWidget(\"toggle\",\"value\",true,\"value\");\r\n        this.serialize_widgets = true;\r\n        this.widgets_up = true;\r\n        this.size = [140, 30];\r\n    }\r\n\r\n    ConstantBoolean.title = \"Const Boolean\";\r\n    ConstantBoolean.desc = \"Constant boolean\";\r\n    ConstantBoolean.prototype.getTitle = ConstantNumber.prototype.getTitle;\r\n\r\n    ConstantBoolean.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.properties[\"value\"]);\r\n    };\r\n\r\n\tConstantBoolean.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n\tConstantBoolean.prototype.onGetInputs = function() {\r\n\t\treturn [[\"toggle\", LiteGraph.ACTION]];\r\n\t};\r\n\r\n\tConstantBoolean.prototype.onAction = function(action)\r\n\t{\r\n\t\tthis.setValue( !this.properties.value );\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/boolean\", ConstantBoolean);\r\n\r\n    function ConstantString() {\r\n        this.addOutput(\"string\", \"string\");\r\n        this.addProperty(\"value\", \"\");\r\n        this.widget = this.addWidget(\"text\",\"value\",\"\",\"value\");  //link to property value\r\n        this.widgets_up = true;\r\n        this.size = [180, 30];\r\n    }\r\n\r\n    ConstantString.title = \"Const String\";\r\n    ConstantString.desc = \"Constant string\";\r\n\r\n    ConstantString.prototype.getTitle = ConstantNumber.prototype.getTitle;\r\n\r\n    ConstantString.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.properties[\"value\"]);\r\n    };\r\n\r\n\tConstantString.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n\tConstantString.prototype.onDropFile = function(file)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tvar reader = new FileReader();\r\n\t\treader.onload = function(e)\r\n\t\t{\r\n\t\t\tthat.setProperty(\"value\",e.target.result);\r\n\t\t}\r\n\t\treader.readAsText(file);\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/string\", ConstantString);\r\n\r\n    function ConstantObject() {\r\n        this.addOutput(\"obj\", \"object\");\r\n        this.size = [120, 30];\r\n\t\tthis._object = {};\r\n    }\r\n\r\n    ConstantObject.title = \"Const Object\";\r\n    ConstantObject.desc = \"Constant Object\";\r\n\r\n    ConstantObject.prototype.onExecute = function() {\r\n        this.setOutputData(0, this._object);\r\n    };\r\n\r\n    LiteGraph.registerNodeType( \"basic/object\", ConstantObject );\r\n\r\n    function ConstantFile() {\r\n        this.addInput(\"url\", \"string\");\r\n        this.addOutput(\"file\", \"string\");\r\n        this.addProperty(\"url\", \"\");\r\n        this.addProperty(\"type\", \"text\");\r\n        this.widget = this.addWidget(\"text\",\"url\",\"\",\"url\");\r\n        this._data = null;\r\n    }\r\n\r\n    ConstantFile.title = \"Const File\";\r\n    ConstantFile.desc = \"Fetches a file from an url\";\r\n    ConstantFile[\"@type\"] = { type: \"enum\", values: [\"text\",\"arraybuffer\",\"blob\",\"json\"] };\r\n\r\n    ConstantFile.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"url\")\r\n\t\t{\r\n\t\t\tif( value == null || value == \"\")\r\n\t\t\t\tthis._data = null;\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.fetchFile(value);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n    ConstantFile.prototype.onExecute = function() {\r\n\t\tvar url = this.getInputData(0) || this.properties.url;\r\n\t\tif(url && (url != this._url || this._type != this.properties.type))\r\n\t\t\tthis.fetchFile(url);\r\n        this.setOutputData(0, this._data );\r\n    };\r\n\r\n\tConstantFile.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n    ConstantFile.prototype.fetchFile = function(url) {\r\n\t\tvar that = this;\r\n\t\tif(!url || url.constructor !== String)\r\n\t\t{\r\n\t\t\tthat._data = null;\r\n            that.boxcolor = null;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis._url = url;\r\n\t\tthis._type = this.properties.type;\r\n        if (url.substr(0, 4) == \"http\" && LiteGraph.proxy) {\r\n            url = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\r\n        }\r\n\t\tfetch(url)\r\n\t\t.then(function(response) {\r\n\t\t\tif(!response.ok)\r\n\t\t\t\t throw new Error(\"File not found\");\r\n\r\n\t\t\tif(that.properties.type == \"arraybuffer\")\r\n\t\t\t\treturn response.arrayBuffer();\r\n\t\t\telse if(that.properties.type == \"text\")\r\n\t\t\t\treturn response.text();\r\n\t\t\telse if(that.properties.type == \"json\")\r\n\t\t\t\treturn response.json();\r\n\t\t\telse if(that.properties.type == \"blob\")\r\n\t\t\t\treturn response.blob();\r\n\t\t})\r\n\t\t.then(function(data) {\r\n\t\t\tthat._data = data;\r\n            that.boxcolor = \"#AEA\";\r\n\t\t})\r\n\t\t.catch(function(error) {\r\n\t\t\tthat._data = null;\r\n            that.boxcolor = \"red\";\r\n\t\t\tconsole.error(\"error fetching file:\",url);\r\n\t\t});\r\n    };\r\n\r\n\tConstantFile.prototype.onDropFile = function(file)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tthis._url = file.name;\r\n\t\tthis._type = this.properties.type;\r\n\t\tthis.properties.url = file.name;\r\n\t\tvar reader = new FileReader();\r\n\t\treader.onload = function(e)\r\n\t\t{\r\n            that.boxcolor = \"#AEA\";\r\n\t\t\tvar v = e.target.result;\r\n\t\t\tif( that.properties.type == \"json\" )\r\n\t\t\t\tv = JSON.parse(v);\r\n\t\t\tthat._data = v;\r\n\t\t}\r\n\t\tif(that.properties.type == \"arraybuffer\")\r\n\t\t\treader.readAsArrayBuffer(file);\r\n\t\telse if(that.properties.type == \"text\" || that.properties.type == \"json\")\r\n\t\t\treader.readAsText(file);\r\n\t\telse if(that.properties.type == \"blob\")\r\n\t\t\treturn reader.readAsBinaryString(file);\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/file\", ConstantFile);\r\n\r\n\r\n//to store json objects\r\nfunction JSONParse() {\r\n\tthis.addInput(\"parse\", LiteGraph.ACTION);\r\n\tthis.addInput(\"json\", \"string\");\r\n\tthis.addOutput(\"done\", LiteGraph.EVENT);\r\n\tthis.addOutput(\"object\", \"object\");\r\n\tthis.widget = this.addWidget(\"button\",\"parse\",\"\",this.parse.bind(this));\r\n\tthis._str = null;\r\n\tthis._obj = null;\r\n}\r\n\r\nJSONParse.title = \"JSON Parse\";\r\nJSONParse.desc = \"Parses JSON String into object\";\r\n\r\nJSONParse.prototype.parse = function()\r\n{\r\n\tif(!this._str)\r\n\t\treturn;\r\n\r\n\ttry {\r\n\t\tthis._str = this.getInputData(1);\r\n\t\tthis._obj = JSON.parse(this._str);\r\n\t\tthis.boxcolor = \"#AEA\";\r\n\t\tthis.triggerSlot(0);\r\n\t} catch (err) {\r\n\t\tthis.boxcolor = \"red\";\r\n\t}\r\n}\r\n\r\nJSONParse.prototype.onExecute = function() {\r\n\tthis._str = this.getInputData(1);\r\n\tthis.setOutputData(1, this._obj);\r\n};\r\n\r\nJSONParse.prototype.onAction = function(name) {\r\n\tif(name == \"parse\")\r\n\t\tthis.parse();\r\n}\r\n\r\nLiteGraph.registerNodeType(\"basic/jsonparse\", JSONParse);\t\r\n\r\n\t//to store json objects\r\n    function ConstantData() {\r\n        this.addOutput(\"data\", \"object\");\r\n        this.addProperty(\"value\", \"\");\r\n        this.widget = this.addWidget(\"text\",\"json\",\"\",\"value\");\r\n        this.widgets_up = true;\r\n        this.size = [140, 30];\r\n        this._value = null;\r\n    }\r\n\r\n    ConstantData.title = \"Const Data\";\r\n    ConstantData.desc = \"Constant Data\";\r\n\r\n    ConstantData.prototype.onPropertyChanged = function(name, value) {\r\n        this.widget.value = value;\r\n        if (value == null || value == \"\") {\r\n            return;\r\n        }\r\n\r\n        try {\r\n            this._value = JSON.parse(value);\r\n            this.boxcolor = \"#AEA\";\r\n        } catch (err) {\r\n            this.boxcolor = \"red\";\r\n        }\r\n    };\r\n\r\n    ConstantData.prototype.onExecute = function() {\r\n        this.setOutputData(0, this._value);\r\n    };\r\n\r\n\tConstantData.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n    LiteGraph.registerNodeType(\"basic/data\", ConstantData);\r\n\r\n\t//to store json objects\r\n    function ConstantArray() {\r\n\t\tthis._value = [];\r\n        this.addInput(\"json\", \"\");\r\n        this.addOutput(\"arrayOut\", \"array\");\r\n\t\tthis.addOutput(\"length\", \"number\");\r\n        this.addProperty(\"value\", \"[]\");\r\n        this.widget = this.addWidget(\"text\",\"array\",this.properties.value,\"value\");\r\n        this.widgets_up = true;\r\n        this.size = [140, 50];\r\n    }\r\n\r\n    ConstantArray.title = \"Const Array\";\r\n    ConstantArray.desc = \"Constant Array\";\r\n\r\n    ConstantArray.prototype.onPropertyChanged = function(name, value) {\r\n        this.widget.value = value;\r\n        if (value == null || value == \"\") {\r\n            return;\r\n        }\r\n\r\n        try {\r\n\t\t\tif(value[0] != \"[\")\r\n\t            this._value = JSON.parse(\"[\" + value + \"]\");\r\n\t\t\telse\r\n\t            this._value = JSON.parse(value);\r\n            this.boxcolor = \"#AEA\";\r\n        } catch (err) {\r\n            this.boxcolor = \"red\";\r\n        }\r\n    };\r\n\r\n    ConstantArray.prototype.onExecute = function() {\r\n        var v = this.getInputData(0);\r\n\t\tif(v && v.length) //clone\r\n\t\t{\r\n\t\t\tif(!this._value)\r\n\t\t\t\tthis._value = new Array();\r\n\t\t\tthis._value.length = v.length;\r\n\t\t\tfor(var i = 0; i < v.length; ++i)\r\n\t\t\t\tthis._value[i] = v[i];\r\n\t\t}\r\n\t\tthis.setOutputData(0, this._value);\r\n\t\tthis.setOutputData(1, this._value ? ( this._value.length || 0) : 0 );\r\n    };\r\n\r\n\tConstantArray.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n    LiteGraph.registerNodeType(\"basic/array\", ConstantArray);\r\n\r\n\tfunction SetArray()\r\n\t{\r\n        this.addInput(\"arr\", \"array\");\r\n        this.addInput(\"value\", \"\");\r\n        this.addOutput(\"arr\", \"array\");\r\n\t\tthis.properties = { index: 0 };\r\n        this.widget = this.addWidget(\"number\",\"i\",this.properties.index,\"index\",{precision: 0, step: 10, min: 0});\r\n\t}\r\n\r\n    SetArray.title = \"Set Array\";\r\n    SetArray.desc = \"Sets index of array\";\r\n\r\n    SetArray.prototype.onExecute = function() {\r\n        var arr = this.getInputData(0);\r\n\t\tif(!arr)\r\n\t\t\treturn;\r\n        var v = this.getInputData(1);\r\n\t\tif(v === undefined )\r\n\t\t\treturn;\r\n\t\tif(this.properties.index)\r\n\t\t\tarr[ Math.floor(this.properties.index) ] = v;\r\n\t\tthis.setOutputData(0,arr);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/set_array\", SetArray );\r\n\r\n    function ArrayElement() {\r\n        this.addInput(\"array\", \"array,table,string\");\r\n        this.addInput(\"index\", \"number\");\r\n        this.addOutput(\"value\", \"\");\r\n\t\tthis.addProperty(\"index\",0);\r\n    }\r\n\r\n    ArrayElement.title = \"Array[i]\";\r\n    ArrayElement.desc = \"Returns an element from an array\";\r\n\r\n    ArrayElement.prototype.onExecute = function() {\r\n        var array = this.getInputData(0);\r\n        var index = this.getInputData(1);\r\n\t\tif(index == null)\r\n\t\t\tindex = this.properties.index;\r\n\t\tif(array == null || index == null )\r\n\t\t\treturn;\r\n        this.setOutputData(0, array[Math.floor(Number(index))] );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/array[]\", ArrayElement);\r\n\r\n    function TableElement() {\r\n        this.addInput(\"table\", \"table\");\r\n        this.addInput(\"row\", \"number\");\r\n        this.addInput(\"col\", \"number\");\r\n        this.addOutput(\"value\", \"\");\r\n\t\tthis.addProperty(\"row\",0);\r\n\t\tthis.addProperty(\"column\",0);\r\n    }\r\n\r\n    TableElement.title = \"Table[row][col]\";\r\n    TableElement.desc = \"Returns an element from a table\";\r\n\r\n    TableElement.prototype.onExecute = function() {\r\n        var table = this.getInputData(0);\r\n        var row = this.getInputData(1);\r\n        var col = this.getInputData(2);\r\n\t\tif(row == null)\r\n\t\t\trow = this.properties.row;\r\n\t\tif(col == null)\r\n\t\t\tcol = this.properties.column;\r\n\t\tif(table == null || row == null || col == null)\r\n\t\t\treturn;\r\n\t\tvar row = table[Math.floor(Number(row))];\r\n\t\tif(row)\r\n\t        this.setOutputData(0, row[Math.floor(Number(col))] );\r\n\t\telse\r\n\t        this.setOutputData(0, null );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/table[][]\", TableElement);\r\n\r\n    function ObjectProperty() {\r\n        this.addInput(\"obj\", \"object\");\r\n        this.addOutput(\"property\", 0);\r\n        this.addProperty(\"value\", 0);\r\n        this.widget = this.addWidget(\"text\",\"prop.\",\"\",this.setValue.bind(this) );\r\n        this.widgets_up = true;\r\n        this.size = [140, 30];\r\n        this._value = null;\r\n    }\r\n\r\n    ObjectProperty.title = \"Object property\";\r\n    ObjectProperty.desc = \"Outputs the property of an object\";\r\n\r\n    ObjectProperty.prototype.setValue = function(v) {\r\n        this.properties.value = v;\r\n        this.widget.value = v;\r\n    };\r\n\r\n    ObjectProperty.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return \"in.\" + this.properties.value;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    ObjectProperty.prototype.onPropertyChanged = function(name, value) {\r\n        this.widget.value = value;\r\n    };\r\n\r\n    ObjectProperty.prototype.onExecute = function() {\r\n        var data = this.getInputData(0);\r\n        if (data != null) {\r\n            this.setOutputData(0, data[this.properties.value]);\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/object_property\", ObjectProperty);\r\n\r\n    function ObjectKeys() {\r\n        this.addInput(\"obj\", \"\");\r\n        this.addOutput(\"keys\", \"array\");\r\n        this.size = [140, 30];\r\n    }\r\n\r\n    ObjectKeys.title = \"Object keys\";\r\n    ObjectKeys.desc = \"Outputs an array with the keys of an object\";\r\n\r\n    ObjectKeys.prototype.onExecute = function() {\r\n        var data = this.getInputData(0);\r\n        if (data != null) {\r\n            this.setOutputData(0, Object.keys(data) );\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/object_keys\", ObjectKeys);\r\n\r\n\r\n\tfunction SetObject()\r\n\t{\r\n        this.addInput(\"obj\", \"\");\r\n        this.addInput(\"value\", \"\");\r\n        this.addOutput(\"obj\", \"\");\r\n\t\tthis.properties = { property: \"\" };\r\n        this.name_widget = this.addWidget(\"text\",\"prop.\",this.properties.property,\"property\");\r\n\t}\r\n\r\n    SetObject.title = \"Set Object\";\r\n    SetObject.desc = \"Adds propertiesrty to object\";\r\n\r\n    SetObject.prototype.onExecute = function() {\r\n        var obj = this.getInputData(0);\r\n\t\tif(!obj)\r\n\t\t\treturn;\r\n        var v = this.getInputData(1);\r\n\t\tif(v === undefined )\r\n\t\t\treturn;\r\n\t\tif(this.properties.property)\r\n\t\t\tobj[ this.properties.property ] = v;\r\n\t\tthis.setOutputData(0,obj);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/set_object\", SetObject );\r\n\r\n\r\n    function MergeObjects() {\r\n        this.addInput(\"A\", \"object\");\r\n        this.addInput(\"B\", \"object\");\r\n        this.addOutput(\"out\", \"object\");\r\n\t\tthis._result = {};\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"clear\",\"\",function(){\r\n\t\t\tthat._result = {};\r\n\t\t});\r\n\t\tthis.size = this.computeSize();\r\n    }\r\n\r\n    MergeObjects.title = \"Merge Objects\";\r\n    MergeObjects.desc = \"Creates an object copying properties from others\";\r\n\r\n    MergeObjects.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        var B = this.getInputData(1);\r\n\t\tvar C = this._result;\r\n\t\tif(A)\r\n\t\t\tfor(var i in A)\r\n\t\t\t\tC[i] = A[i];\r\n\t\tif(B)\r\n\t\t\tfor(var i in B)\r\n\t\t\t\tC[i] = B[i];\r\n\t\tthis.setOutputData(0,C);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/merge_objects\", MergeObjects );\r\n\r\n    //Store as variable\r\n    function Variable() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"in\");\r\n        this.addOutput(\"out\");\r\n\t\tthis.properties = { varname: \"myname\", container: Variable.LITEGRAPH };\r\n        this.value = null;\r\n    }\r\n\r\n    Variable.title = \"Variable\";\r\n    Variable.desc = \"store/read variable value\";\r\n\r\n\tVariable.LITEGRAPH = 0; //between all graphs\r\n\tVariable.GRAPH = 1;\t//only inside this graph\r\n\tVariable.GLOBALSCOPE = 2;\t//attached to Window\r\n\r\n    Variable[\"@container\"] = { type: \"enum\", values: {\"litegraph\":Variable.LITEGRAPH, \"graph\":Variable.GRAPH,\"global\": Variable.GLOBALSCOPE} };\r\n\r\n    Variable.prototype.onExecute = function() {\r\n\t\tvar container = this.getContainer();\r\n\r\n\t\tif(this.isInputConnected(0))\r\n\t\t{\r\n\t\t\tthis.value = this.getInputData(0);\r\n\t\t\tcontainer[ this.properties.varname ] = this.value;\r\n\t\t\tthis.setOutputData(0, this.value );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData( 0, container[ this.properties.varname ] );\r\n    };\r\n\r\n\tVariable.prototype.getContainer = function()\r\n\t{\r\n\t\tswitch(this.properties.container)\r\n\t\t{\r\n\t\t\tcase Variable.GRAPH:\r\n\t\t\t\tif(this.graph)\r\n\t\t\t\t\treturn this.graph.vars;\r\n\t\t\t\treturn {};\r\n\t\t\t\tbreak;\r\n\t\t\tcase Variable.GLOBALSCOPE:\r\n\t\t\t\treturn global;\r\n\t\t\t\tbreak;\r\n\t\t\tcase Variable.LITEGRAPH:\r\n\t\t\tdefault:\r\n\t\t\t\treturn LiteGraph.Globals;\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n    Variable.prototype.getTitle = function() {\r\n        return this.properties.varname;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/variable\", Variable);\r\n\r\n    function length(v) {\r\n        if(v && v.length != null)\r\n\t\t\treturn Number(v.length);\r\n\t\treturn 0;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"basic/length\",\r\n        length,\r\n        [\"\"],\r\n        \"number\"\r\n    );\r\n\r\n    function length(v) {\r\n        if(v && v.length != null)\r\n\t\t\treturn Number(v.length);\r\n\t\treturn 0;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"basic/not\",\r\n        function(a){ return !a; },\r\n        [\"\"],\r\n        \"boolean\"\r\n    );\r\n\r\n\tfunction DownloadData() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"data\", 0 );\r\n        this.addInput(\"download\", LiteGraph.ACTION );\r\n\t\tthis.properties = { filename: \"data.json\" };\r\n        this.value = null;\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"Download\",\"\", function(v){\r\n\t\t\tif(!that.value)\r\n\t\t\t\treturn;\r\n\t\t\tthat.downloadAsFile();\r\n\t\t});\r\n    }\r\n\r\n    DownloadData.title = \"Download\";\r\n    DownloadData.desc = \"Download some data\";\r\n\r\n\tDownloadData.prototype.downloadAsFile = function()\r\n\t{\r\n\t\tif(this.value == null)\r\n\t\t\treturn;\r\n\r\n\t\tvar str = null;\r\n\t\tif(this.value.constructor === String)\r\n\t\t\tstr = this.value;\r\n\t\telse\r\n\t\t\tstr = JSON.stringify(this.value);\r\n\r\n\t\tvar file = new Blob([str]);\r\n\t\tvar url = URL.createObjectURL( file );\r\n\t\tvar element = document.createElement(\"a\");\r\n\t\telement.setAttribute('href', url);\r\n\t\telement.setAttribute('download', this.properties.filename );\r\n\t\telement.style.display = 'none';\r\n\t\tdocument.body.appendChild(element);\r\n\t\telement.click();\r\n\t\tdocument.body.removeChild(element);\r\n\t\tsetTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url\r\n\t}\r\n\r\n    DownloadData.prototype.onAction = function(action, param) {\r\n\t\tvar that = this;\r\n\t\tsetTimeout( function(){ that.downloadAsFile(); }, 100); //deferred to avoid blocking the renderer with the popup\r\n\t}\r\n\r\n    DownloadData.prototype.onExecute = function() {\r\n        if (this.inputs[0]) {\r\n            this.value = this.getInputData(0);\r\n        }\r\n    };\r\n\r\n    DownloadData.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.filename;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/download\", DownloadData);\r\n\r\n\r\n\r\n    //Watch a value in the editor\r\n    function Watch() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"value\", 0, { label: \"\" });\r\n        this.value = 0;\r\n    }\r\n\r\n    Watch.title = \"Watch\";\r\n    Watch.desc = \"Show value of input\";\r\n\r\n    Watch.prototype.onExecute = function() {\r\n        if (this.inputs[0]) {\r\n            this.value = this.getInputData(0);\r\n        }\r\n    };\r\n\r\n    Watch.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.inputs[0].label;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    Watch.toString = function(o) {\r\n        if (o == null) {\r\n            return \"null\";\r\n        } else if (o.constructor === Number) {\r\n            return o.toFixed(3);\r\n        } else if (o.constructor === Array) {\r\n            var str = \"[\";\r\n            for (var i = 0; i < o.length; ++i) {\r\n                str += Watch.toString(o[i]) + (i + 1 != o.length ? \",\" : \"\");\r\n            }\r\n            str += \"]\";\r\n            return str;\r\n        } else {\r\n            return String(o);\r\n        }\r\n    };\r\n\r\n    Watch.prototype.onDrawBackground = function(ctx) {\r\n        //show the current value\r\n        this.inputs[0].label = Watch.toString(this.value);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/watch\", Watch);\r\n\r\n    //in case one type doesnt match other type but you want to connect them anyway\r\n    function Cast() {\r\n        this.addInput(\"in\", 0);\r\n        this.addOutput(\"out\", 0);\r\n        this.size = [40, 30];\r\n    }\r\n\r\n    Cast.title = \"Cast\";\r\n    Cast.desc = \"Allows to connect different types\";\r\n\r\n    Cast.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.getInputData(0));\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/cast\", Cast);\r\n\r\n    //Show value inside the debug console\r\n    function Console() {\r\n        this.mode = LiteGraph.ON_EVENT;\r\n        this.size = [80, 30];\r\n        this.addProperty(\"msg\", \"\");\r\n        this.addInput(\"log\", LiteGraph.EVENT);\r\n        this.addInput(\"msg\", 0);\r\n    }\r\n\r\n    Console.title = \"Console\";\r\n    Console.desc = \"Show value inside the console\";\r\n\r\n    Console.prototype.onAction = function(action, param) {\r\n        // param is the action\r\n        var msg = this.getInputData(1); //getInputDataByName(\"msg\");\r\n        //if (msg == null || typeof msg == \"undefined\") return;\r\n        if (!msg) msg = this.properties.msg;\r\n        if (!msg) msg = \"Event: \"+param; // msg is undefined if the slot is lost?\r\n        if (action == \"log\") {\r\n            console.log(msg);\r\n        } else if (action == \"warn\") {\r\n            console.warn(msg);\r\n        } else if (action == \"error\") {\r\n            console.error(msg);\r\n        }\r\n    };\r\n\r\n    Console.prototype.onExecute = function() {\r\n        var msg = this.getInputData(1); //getInputDataByName(\"msg\");\r\n        if (!msg) msg = this.properties.msg;\r\n        if (msg != null && typeof msg != \"undefined\") {\r\n            this.properties.msg = msg;\r\n            console.log(msg);\r\n        }\r\n    };\r\n\r\n    Console.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"log\", LiteGraph.ACTION],\r\n            [\"warn\", LiteGraph.ACTION],\r\n            [\"error\", LiteGraph.ACTION]\r\n        ];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/console\", Console);\r\n\r\n    //Show value inside the debug console\r\n    function Alert() {\r\n        this.mode = LiteGraph.ON_EVENT;\r\n        this.addProperty(\"msg\", \"\");\r\n        this.addInput(\"\", LiteGraph.EVENT);\r\n        var that = this;\r\n        this.widget = this.addWidget(\"text\", \"Text\", \"\", \"msg\");\r\n        this.widgets_up = true;\r\n        this.size = [200, 30];\r\n    }\r\n\r\n    Alert.title = \"Alert\";\r\n    Alert.desc = \"Show an alert window\";\r\n    Alert.color = \"#510\";\r\n\r\n    Alert.prototype.onConfigure = function(o) {\r\n        this.widget.value = o.properties.msg;\r\n    };\r\n\r\n    Alert.prototype.onAction = function(action, param) {\r\n        var msg = this.properties.msg;\r\n        setTimeout(function() {\r\n            alert(msg);\r\n        }, 10);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/alert\", Alert);\r\n\r\n    //Execites simple code\r\n    function NodeScript() {\r\n        this.size = [60, 30];\r\n        this.addProperty(\"onExecute\", \"return A;\");\r\n        this.addInput(\"A\", 0);\r\n        this.addInput(\"B\", 0);\r\n        this.addOutput(\"out\", 0);\r\n\r\n        this._func = null;\r\n        this.data = {};\r\n    }\r\n\r\n    NodeScript.prototype.onConfigure = function(o) {\r\n        if (o.properties.onExecute && LiteGraph.allow_scripts)\r\n            this.compileCode(o.properties.onExecute);\r\n\t\telse\r\n\t\t\tconsole.warn(\"Script not compiled, LiteGraph.allow_scripts is false\");\r\n    };\r\n\r\n    NodeScript.title = \"Script\";\r\n    NodeScript.desc = \"executes a code (max 256 characters)\";\r\n\r\n    NodeScript.widgets_info = {\r\n        onExecute: { type: \"code\" }\r\n    };\r\n\r\n    NodeScript.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"onExecute\" && LiteGraph.allow_scripts)\r\n            this.compileCode(value);\r\n\t\telse\r\n\t\t\tconsole.warn(\"Script not compiled, LiteGraph.allow_scripts is false\");\r\n    };\r\n\r\n    NodeScript.prototype.compileCode = function(code) {\r\n        this._func = null;\r\n        if (code.length > 256) {\r\n            console.warn(\"Script too long, max 256 chars\");\r\n        } else {\r\n            var code_low = code.toLowerCase();\r\n            var forbidden_words = [\r\n                \"script\",\r\n                \"body\",\r\n                \"document\",\r\n                \"eval\",\r\n                \"nodescript\",\r\n                \"function\"\r\n            ]; //bad security solution\r\n            for (var i = 0; i < forbidden_words.length; ++i) {\r\n                if (code_low.indexOf(forbidden_words[i]) != -1) {\r\n                    console.warn(\"invalid script\");\r\n                    return;\r\n                }\r\n            }\r\n            try {\r\n                this._func = new Function(\"A\", \"B\", \"C\", \"DATA\", \"node\", code);\r\n            } catch (err) {\r\n                console.error(\"Error parsing script\");\r\n                console.error(err);\r\n            }\r\n        }\r\n    };\r\n\r\n    NodeScript.prototype.onExecute = function() {\r\n        if (!this._func) {\r\n            return;\r\n        }\r\n\r\n        try {\r\n            var A = this.getInputData(0);\r\n            var B = this.getInputData(1);\r\n            var C = this.getInputData(2);\r\n            this.setOutputData(0, this._func(A, B, C, this.data, this));\r\n        } catch (err) {\r\n            console.error(\"Error in script\");\r\n            console.error(err);\r\n        }\r\n    };\r\n\r\n    NodeScript.prototype.onGetOutputs = function() {\r\n        return [[\"C\", \"\"]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/script\", NodeScript);\r\n    \r\n    \r\n    function GenericCompare() {\r\n        this.addInput(\"A\", 0);\r\n        this.addInput(\"B\", 0);\r\n        this.addOutput(\"true\", \"boolean\");\r\n        this.addOutput(\"false\", \"boolean\");\r\n        this.addProperty(\"A\", 1);\r\n        this.addProperty(\"B\", 1);\r\n        this.addProperty(\"OP\", \"==\", \"enum\", { values: GenericCompare.values });\r\n\t\tthis.addWidget(\"combo\",\"Op.\",this.properties.OP,{ property: \"OP\", values: GenericCompare.values } );\r\n\r\n        this.size = [80, 60];\r\n    }\r\n\r\n    GenericCompare.values = [\"==\", \"!=\"]; //[\">\", \"<\", \"==\", \"!=\", \"<=\", \">=\", \"||\", \"&&\" ];\r\n    GenericCompare[\"@OP\"] = {\r\n        type: \"enum\",\r\n        title: \"operation\",\r\n        values: GenericCompare.values\r\n    };\r\n\r\n    GenericCompare.title = \"Compare *\";\r\n    GenericCompare.desc = \"evaluates condition between A and B\";\r\n\r\n    GenericCompare.prototype.getTitle = function() {\r\n        return \"*A \" + this.properties.OP + \" *B\";\r\n    };\r\n\r\n    GenericCompare.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        if (A === undefined) {\r\n            A = this.properties.A;\r\n        } else {\r\n            this.properties.A = A;\r\n        }\r\n\r\n        var B = this.getInputData(1);\r\n        if (B === undefined) {\r\n            B = this.properties.B;\r\n        } else {\r\n            this.properties.B = B;\r\n        }\r\n\r\n        var result = false;\r\n        if (typeof A == typeof B){\r\n            switch (this.properties.OP) {\r\n                case \"==\":\r\n                case \"!=\":\r\n                    // traverse both objects.. consider that this is not a true deep check! consider underscore or other library for thath :: _isEqual()\r\n                    result = true;\r\n                    switch(typeof A){\r\n                        case \"object\":\r\n                            var aProps = Object.getOwnPropertyNames(A);\r\n                            var bProps = Object.getOwnPropertyNames(B);\r\n                            if (aProps.length != bProps.length){\r\n                                result = false;\r\n                                break;\r\n                            }\r\n                            for (var i = 0; i < aProps.length; i++) {\r\n                                var propName = aProps[i];\r\n                                if (A[propName] !== B[propName]) {\r\n                                    result = false;\r\n                                    break;\r\n                                }\r\n                            }\r\n                        break;\r\n                        default:\r\n                            result = A == B;\r\n                    }\r\n                    if (this.properties.OP == \"!=\") result = !result;\r\n                    break;\r\n                /*case \">\":\r\n                    result = A > B;\r\n                    break;\r\n                case \"<\":\r\n                    result = A < B;\r\n                    break;\r\n                case \"<=\":\r\n                    result = A <= B;\r\n                    break;\r\n                case \">=\":\r\n                    result = A >= B;\r\n                    break;\r\n                case \"||\":\r\n                    result = A || B;\r\n                    break;\r\n                case \"&&\":\r\n                    result = A && B;\r\n                    break;*/\r\n            }\r\n        }\r\n        this.setOutputData(0, result);\r\n        this.setOutputData(1, !result);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/CompareValues\", GenericCompare);\r\n    \r\n})(this);\r\n\n//event related nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    //Show value inside the debug console\r\n    function LogEvent() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"event\", LiteGraph.ACTION);\r\n    }\r\n\r\n    LogEvent.title = \"Log Event\";\r\n    LogEvent.desc = \"Log event in console\";\r\n\r\n    LogEvent.prototype.onAction = function(action, param, options) {\r\n        console.log(action, param);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/log\", LogEvent);\r\n\r\n    //convert to Event if the value is true\r\n    function TriggerEvent() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"if\", \"\");\r\n        this.addOutput(\"true\", LiteGraph.EVENT);\r\n        this.addOutput(\"change\", LiteGraph.EVENT);\r\n        this.addOutput(\"false\", LiteGraph.EVENT);\r\n\t\tthis.properties = { only_on_change: true };\r\n\t\tthis.prev = 0;\r\n    }\r\n\r\n    TriggerEvent.title = \"TriggerEvent\";\r\n    TriggerEvent.desc = \"Triggers event if input evaluates to true\";\r\n\r\n    TriggerEvent.prototype.onExecute = function( param, options) {\r\n\t\tvar v = this.getInputData(0);\r\n\t\tvar changed = (v != this.prev);\r\n\t\tif(this.prev === 0)\r\n\t\t\tchanged = false;\r\n\t\tvar must_resend = (changed && this.properties.only_on_change) || (!changed && !this.properties.only_on_change);\r\n\t\tif(v && must_resend )\r\n\t        this.triggerSlot(0, param, null, options);\r\n\t\tif(!v && must_resend)\r\n\t        this.triggerSlot(2, param, null, options);\r\n\t\tif(changed)\r\n\t        this.triggerSlot(1, param, null, options);\r\n\t\tthis.prev = v;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/trigger\", TriggerEvent);\r\n\r\n    //Sequence of events\r\n    function Sequence() {\r\n\t\tvar that = this;\r\n        this.addInput(\"\", LiteGraph.ACTION);\r\n        this.addInput(\"\", LiteGraph.ACTION);\r\n        this.addInput(\"\", LiteGraph.ACTION);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addWidget(\"button\",\"+\",null,function(){\r\n\t        that.addInput(\"\", LiteGraph.ACTION);\r\n\t        that.addOutput(\"\", LiteGraph.EVENT);\r\n        });\r\n        this.size = [90, 70];\r\n        this.flags = { horizontal: true, render_box: false };\r\n    }\r\n\r\n    Sequence.title = \"Sequence\";\r\n    Sequence.desc = \"Triggers a sequence of events when an event arrives\";\r\n\r\n    Sequence.prototype.getTitle = function() {\r\n        return \"\";\r\n    };\r\n\r\n    Sequence.prototype.onAction = function(action, param, options) {\r\n        if (this.outputs) {\r\n            options = options || {};\r\n            for (var i = 0; i < this.outputs.length; ++i) {\r\n\t\t\t\tvar output = this.outputs[i];\r\n\t\t\t\t//needs more info about this...\r\n\t\t\t\tif( options.action_call ) // CREATE A NEW ID FOR THE ACTION\r\n\t                options.action_call = options.action_call + \"_seq_\" + i;\r\n\t\t\t\telse\r\n\t\t\t\t\toptions.action_call = this.id + \"_\" + (action ? action : \"action\")+\"_seq_\"+i+\"_\"+Math.floor(Math.random()*9999);\r\n                this.triggerSlot(i, param, null, options);\r\n            }\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/sequence\", Sequence);\r\n\r\n\r\n   //Sequence of events\r\n   function WaitAll() {\r\n    var that = this;\r\n    this.addInput(\"\", LiteGraph.ACTION);\r\n    this.addInput(\"\", LiteGraph.ACTION);\r\n    this.addOutput(\"\", LiteGraph.EVENT);\r\n    this.addWidget(\"button\",\"+\",null,function(){\r\n        that.addInput(\"\", LiteGraph.ACTION);\r\n        that.size[0] = 90;\r\n    });\r\n    this.size = [90, 70];\r\n    this.ready = [];\r\n}\r\n\r\nWaitAll.title = \"WaitAll\";\r\nWaitAll.desc = \"Wait until all input events arrive then triggers output\";\r\n\r\nWaitAll.prototype.getTitle = function() {\r\n    return \"\";\r\n};\r\n\r\nWaitAll.prototype.onDrawBackground = function(ctx)\r\n{\r\n    if (this.flags.collapsed) {\r\n        return;\r\n    }\r\n    for(var i = 0; i < this.inputs.length; ++i)\r\n    {\r\n        var y = i * LiteGraph.NODE_SLOT_HEIGHT + 10;\r\n        ctx.fillStyle = this.ready[i] ? \"#AFB\" : \"#000\";\r\n        ctx.fillRect(20, y, 10, 10);\r\n    }\r\n}\r\n\r\nWaitAll.prototype.onAction = function(action, param, options, slot_index) {\r\n    if(slot_index == null)\r\n        return;\r\n\r\n    //check all\r\n    this.ready.length = this.outputs.length;\r\n    this.ready[slot_index] = true;\r\n    for(var i = 0; i < this.ready.length;++i)\r\n        if(!this.ready[i])\r\n            return;\r\n    //pass\r\n    this.reset();\r\n    this.triggerSlot(0);\r\n};\r\n\r\nWaitAll.prototype.reset = function()\r\n{\r\n    this.ready.length = 0;\r\n}\r\n\r\nLiteGraph.registerNodeType(\"events/waitAll\", WaitAll);    \r\n\r\n\r\n    //Sequencer for events\r\n    function Stepper() {\r\n\t\tvar that = this;\r\n\t\tthis.properties = { index: 0 };\r\n        this.addInput(\"index\", \"number\");\r\n        this.addInput(\"step\", LiteGraph.ACTION);\r\n        this.addInput(\"reset\", LiteGraph.ACTION);\r\n        this.addOutput(\"index\", \"number\");\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT,{removable:true});\r\n        this.addWidget(\"button\",\"+\",null,function(){\r\n\t        that.addOutput(\"\", LiteGraph.EVENT, {removable:true});\r\n        });\r\n        this.size = [120, 120];\r\n        this.flags = { render_box: false };\r\n    }\r\n\r\n    Stepper.title = \"Stepper\";\r\n    Stepper.desc = \"Trigger events sequentially when an tick arrives\";\r\n\r\n\tStepper.prototype.onDrawBackground = function(ctx)\r\n\t{\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\t\tvar index = this.properties.index || 0;\r\n        ctx.fillStyle = \"#AFB\";\r\n\t\tvar w = this.size[0];\r\n        var y = (index + 1)* LiteGraph.NODE_SLOT_HEIGHT + 4;\r\n        ctx.beginPath();\r\n        ctx.moveTo(w - 30, y);\r\n        ctx.lineTo(w - 30, y + LiteGraph.NODE_SLOT_HEIGHT);\r\n        ctx.lineTo(w - 15, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5);\r\n        ctx.fill();\r\n\t}\r\n\r\n\tStepper.prototype.onExecute = function()\r\n\t{\r\n\t\tvar index = this.getInputData(0);\r\n\t\tif(index != null)\r\n\t\t{\r\n\t\t\tindex = Math.floor(index);\r\n\t\t\tindex = clamp( index, 0, this.outputs ? (this.outputs.length - 2) : 0 );\r\n\t\t\tif( index != this.properties.index )\r\n\t\t\t{\r\n\t\t\t\tthis.properties.index = index;\r\n\t\t\t    this.triggerSlot( index+1 );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0, this.properties.index );\r\n\t}\r\n\r\n    Stepper.prototype.onAction = function(action, param) {\r\n\t\tif(action == \"reset\")\r\n\t\t\tthis.properties.index = 0;\r\n\t\telse if(action == \"step\")\r\n\t\t{\r\n            this.triggerSlot(this.properties.index+1, param);\r\n\t\t\tvar n = this.outputs ? this.outputs.length - 1 : 0;\r\n\t\t\tthis.properties.index = (this.properties.index + 1) % n;\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/stepper\", Stepper);\r\n\r\n    //Filter events\r\n    function FilterEvent() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"event\", LiteGraph.ACTION);\r\n        this.addOutput(\"event\", LiteGraph.EVENT);\r\n        this.properties = {\r\n            equal_to: \"\",\r\n            has_property: \"\",\r\n            property_equal_to: \"\"\r\n        };\r\n    }\r\n\r\n    FilterEvent.title = \"Filter Event\";\r\n    FilterEvent.desc = \"Blocks events that do not match the filter\";\r\n\r\n    FilterEvent.prototype.onAction = function(action, param, options) {\r\n        if (param == null) {\r\n            return;\r\n        }\r\n\r\n        if (this.properties.equal_to && this.properties.equal_to != param) {\r\n            return;\r\n        }\r\n\r\n        if (this.properties.has_property) {\r\n            var prop = param[this.properties.has_property];\r\n            if (prop == null) {\r\n                return;\r\n            }\r\n\r\n            if (\r\n                this.properties.property_equal_to &&\r\n                this.properties.property_equal_to != prop\r\n            ) {\r\n                return;\r\n            }\r\n        }\r\n\r\n        this.triggerSlot(0, param, null, options);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/filter\", FilterEvent);\r\n\r\n\r\n    function EventBranch() {\r\n        this.addInput(\"in\", LiteGraph.ACTION);\r\n        this.addInput(\"cond\", \"boolean\");\r\n        this.addOutput(\"true\", LiteGraph.EVENT);\r\n        this.addOutput(\"false\", LiteGraph.EVENT);\r\n        this.size = [120, 60];\r\n\t\tthis._value = false;\r\n    }\r\n\r\n    EventBranch.title = \"Branch\";\r\n    EventBranch.desc = \"If condition is true, outputs triggers true, otherwise false\";\r\n\r\n    EventBranch.prototype.onExecute = function() {\r\n\t\tthis._value = this.getInputData(1);\r\n\t}\r\n\r\n    EventBranch.prototype.onAction = function(action, param, options) {\r\n        this._value = this.getInputData(1);\r\n\t\tthis.triggerSlot(this._value ? 0 : 1, param, null, options);\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"events/branch\", EventBranch);\r\n\r\n    //Show value inside the debug console\r\n    function EventCounter() {\r\n        this.addInput(\"inc\", LiteGraph.ACTION);\r\n        this.addInput(\"dec\", LiteGraph.ACTION);\r\n        this.addInput(\"reset\", LiteGraph.ACTION);\r\n        this.addOutput(\"change\", LiteGraph.EVENT);\r\n        this.addOutput(\"num\", \"number\");\r\n        this.addProperty(\"doCountExecution\", false, \"boolean\", {name: \"Count Executions\"});\r\n        this.addWidget(\"toggle\",\"Count Exec.\",this.properties.doCountExecution,\"doCountExecution\");\r\n        this.num = 0;\r\n    }\r\n\r\n    EventCounter.title = \"Counter\";\r\n    EventCounter.desc = \"Counts events\";\r\n\r\n    EventCounter.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return String(this.num);\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    EventCounter.prototype.onAction = function(action, param, options) {\r\n        var v = this.num;\r\n        if (action == \"inc\") {\r\n            this.num += 1;\r\n        } else if (action == \"dec\") {\r\n            this.num -= 1;\r\n        } else if (action == \"reset\") {\r\n            this.num = 0;\r\n        }\r\n        if (this.num != v) {\r\n            this.trigger(\"change\", this.num);\r\n        }\r\n    };\r\n\r\n    EventCounter.prototype.onDrawBackground = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n        ctx.fillStyle = \"#AAA\";\r\n        ctx.font = \"20px Arial\";\r\n        ctx.textAlign = \"center\";\r\n        ctx.fillText(this.num, this.size[0] * 0.5, this.size[1] * 0.5);\r\n    };\r\n\r\n    EventCounter.prototype.onExecute = function() {\r\n        if(this.properties.doCountExecution){\r\n            this.num += 1;\r\n        }\r\n        this.setOutputData(1, this.num);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/counter\", EventCounter);\r\n\r\n    //Show value inside the debug console\r\n    function DelayEvent() {\r\n        this.size = [60, 30];\r\n        this.addProperty(\"time_in_ms\", 1000);\r\n        this.addInput(\"event\", LiteGraph.ACTION);\r\n        this.addOutput(\"on_time\", LiteGraph.EVENT);\r\n\r\n        this._pending = [];\r\n    }\r\n\r\n    DelayEvent.title = \"Delay\";\r\n    DelayEvent.desc = \"Delays one event\";\r\n\r\n    DelayEvent.prototype.onAction = function(action, param, options) {\r\n        var time = this.properties.time_in_ms;\r\n        if (time <= 0) {\r\n            this.trigger(null, param, options);\r\n        } else {\r\n            this._pending.push([time, param]);\r\n        }\r\n    };\r\n\r\n    DelayEvent.prototype.onExecute = function(param, options) {\r\n        var dt = this.graph.elapsed_time * 1000; //in ms\r\n\r\n        if (this.isInputConnected(1)) {\r\n            this.properties.time_in_ms = this.getInputData(1);\r\n        }\r\n\r\n        for (var i = 0; i < this._pending.length; ++i) {\r\n            var actionPass = this._pending[i];\r\n            actionPass[0] -= dt;\r\n            if (actionPass[0] > 0) {\r\n                continue;\r\n            }\r\n\r\n            //remove\r\n            this._pending.splice(i, 1);\r\n            --i;\r\n\r\n            //trigger\r\n            this.trigger(null, actionPass[1], options);\r\n        }\r\n    };\r\n\r\n    DelayEvent.prototype.onGetInputs = function() {\r\n        return [[\"event\", LiteGraph.ACTION], [\"time_in_ms\", \"number\"]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/delay\", DelayEvent);\r\n\r\n    //Show value inside the debug console\r\n    function TimerEvent() {\r\n        this.addProperty(\"interval\", 1000);\r\n        this.addProperty(\"event\", \"tick\");\r\n        this.addOutput(\"on_tick\", LiteGraph.EVENT);\r\n        this.time = 0;\r\n        this.last_interval = 1000;\r\n        this.triggered = false;\r\n    }\r\n\r\n    TimerEvent.title = \"Timer\";\r\n    TimerEvent.desc = \"Sends an event every N milliseconds\";\r\n\r\n    TimerEvent.prototype.onStart = function() {\r\n        this.time = 0;\r\n    };\r\n\r\n    TimerEvent.prototype.getTitle = function() {\r\n        return \"Timer: \" + this.last_interval.toString() + \"ms\";\r\n    };\r\n\r\n    TimerEvent.on_color = \"#AAA\";\r\n    TimerEvent.off_color = \"#222\";\r\n\r\n    TimerEvent.prototype.onDrawBackground = function() {\r\n        this.boxcolor = this.triggered\r\n            ? TimerEvent.on_color\r\n            : TimerEvent.off_color;\r\n        this.triggered = false;\r\n    };\r\n\r\n    TimerEvent.prototype.onExecute = function() {\r\n        var dt = this.graph.elapsed_time * 1000; //in ms\r\n\r\n        var trigger = this.time == 0;\r\n\r\n        this.time += dt;\r\n        this.last_interval = Math.max(\r\n            1,\r\n            this.getInputOrProperty(\"interval\") | 0\r\n        );\r\n\r\n        if (\r\n            !trigger &&\r\n            (this.time < this.last_interval || isNaN(this.last_interval))\r\n        ) {\r\n            if (this.inputs && this.inputs.length > 1 && this.inputs[1]) {\r\n                this.setOutputData(1, false);\r\n            }\r\n            return;\r\n        }\r\n\r\n        this.triggered = true;\r\n        this.time = this.time % this.last_interval;\r\n        this.trigger(\"on_tick\", this.properties.event);\r\n        if (this.inputs && this.inputs.length > 1 && this.inputs[1]) {\r\n            this.setOutputData(1, true);\r\n        }\r\n    };\r\n\r\n    TimerEvent.prototype.onGetInputs = function() {\r\n        return [[\"interval\", \"number\"]];\r\n    };\r\n\r\n    TimerEvent.prototype.onGetOutputs = function() {\r\n        return [[\"tick\", \"boolean\"]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/timer\", TimerEvent);\r\n\r\n\r\n\r\n    function SemaphoreEvent() {\r\n        this.addInput(\"go\", LiteGraph.ACTION );\r\n        this.addInput(\"green\", LiteGraph.ACTION );\r\n        this.addInput(\"red\", LiteGraph.ACTION );\r\n        this.addOutput(\"continue\", LiteGraph.EVENT );\r\n        this.addOutput(\"blocked\", LiteGraph.EVENT );\r\n        this.addOutput(\"is_green\", \"boolean\" );\r\n\t\tthis._ready = false;\r\n\t\tthis.properties = {};\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"reset\",\"\",function(){\r\n\t\t\tthat._ready = false;\r\n\t\t});\r\n    }\r\n\r\n    SemaphoreEvent.title = \"Semaphore Event\";\r\n    SemaphoreEvent.desc = \"Until both events are not triggered, it doesnt continue.\";\r\n\r\n\tSemaphoreEvent.prototype.onExecute = function()\r\n\t{\r\n\t\tthis.setOutputData(1,this._ready);\r\n\t\tthis.boxcolor = this._ready ? \"#9F9\" : \"#FA5\";\r\n\t}\r\n\r\n    SemaphoreEvent.prototype.onAction = function(action, param) {\r\n\t\tif( action == \"go\" )\r\n\t\t\tthis.triggerSlot( this._ready ? 0 : 1 );\r\n\t\telse if( action == \"green\" )\r\n\t\t\tthis._ready = true;\r\n\t\telse if( action == \"red\" )\r\n\t\t\tthis._ready = false;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/semaphore\", SemaphoreEvent);\r\n\r\n    function OnceEvent() {\r\n        this.addInput(\"in\", LiteGraph.ACTION );\r\n        this.addInput(\"reset\", LiteGraph.ACTION );\r\n        this.addOutput(\"out\", LiteGraph.EVENT );\r\n\t\tthis._once = false;\r\n\t\tthis.properties = {};\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"reset\",\"\",function(){\r\n\t\t\tthat._once = false;\r\n\t\t});\r\n    }\r\n\r\n    OnceEvent.title = \"Once\";\r\n    OnceEvent.desc = \"Only passes an event once, then gets locked\";\r\n\r\n    OnceEvent.prototype.onAction = function(action, param) {\r\n\t\tif( action == \"in\" && !this._once )\r\n\t\t{\r\n\t\t\tthis._once = true;\r\n\t\t\tthis.triggerSlot( 0, param );\r\n\t\t}\r\n\t\telse if( action == \"reset\" )\r\n\t\t\tthis._once = false;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/once\", OnceEvent);\r\n\r\n    function DataStore() {\r\n        this.addInput(\"data\", 0);\r\n        this.addInput(\"assign\", LiteGraph.ACTION);\r\n        this.addOutput(\"data\", 0);\r\n\t\tthis._last_value = null;\r\n\t\tthis.properties = { data: null, serialize: true };\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"store\",\"\",function(){\r\n\t\t\tthat.properties.data = that._last_value;\r\n\t\t});\r\n    }\r\n\r\n    DataStore.title = \"Data Store\";\r\n    DataStore.desc = \"Stores data and only changes when event is received\";\r\n\r\n\tDataStore.prototype.onExecute = function()\r\n\t{\r\n\t\tthis._last_value = this.getInputData(0);\r\n\t\tthis.setOutputData(0, this.properties.data );\r\n\t}\r\n\r\n    DataStore.prototype.onAction = function(action, param, options) {\r\n\t\tthis.properties.data = this._last_value;\r\n    };\r\n\r\n\tDataStore.prototype.onSerialize = function(o)\r\n\t{\r\n\t\tif(o.data == null)\r\n\t\t\treturn;\r\n\t\tif(this.properties.serialize == false || (o.data.constructor !== String && o.data.constructor !== Number && o.data.constructor !== Boolean && o.data.constructor !== Array && o.data.constructor !== Object ))\r\n\t\t\to.data = null;\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/data_store\", DataStore);\r\n\r\n\r\n\r\n})(this);\r\n\n//widgets\n(function(global) {\n    var LiteGraph = global.LiteGraph;\n\n    /* Button ****************/\n\n    function WidgetButton() {\n        this.addOutput(\"\", LiteGraph.EVENT);\n        this.addOutput(\"\", \"boolean\");\n        this.addProperty(\"text\", \"click me\");\n        this.addProperty(\"font_size\", 30);\n        this.addProperty(\"message\", \"\");\n        this.size = [164, 84];\n        this.clicked = false;\n    }\n\n    WidgetButton.title = \"Button\";\n    WidgetButton.desc = \"Triggers an event\";\n\n    WidgetButton.font = \"Arial\";\n    WidgetButton.prototype.onDrawForeground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n        var margin = 10;\n        ctx.fillStyle = \"black\";\n        ctx.fillRect(\n            margin + 1,\n            margin + 1,\n            this.size[0] - margin * 2,\n            this.size[1] - margin * 2\n        );\n        ctx.fillStyle = \"#AAF\";\n        ctx.fillRect(\n            margin - 1,\n            margin - 1,\n            this.size[0] - margin * 2,\n            this.size[1] - margin * 2\n        );\n        ctx.fillStyle = this.clicked\n            ? \"white\"\n            : this.mouseOver\n            ? \"#668\"\n            : \"#334\";\n        ctx.fillRect(\n            margin,\n            margin,\n            this.size[0] - margin * 2,\n            this.size[1] - margin * 2\n        );\n\n        if (this.properties.text || this.properties.text === 0) {\n            var font_size = this.properties.font_size || 30;\n            ctx.textAlign = \"center\";\n            ctx.fillStyle = this.clicked ? \"black\" : \"white\";\n            ctx.font = font_size + \"px \" + WidgetButton.font;\n            ctx.fillText(\n                this.properties.text,\n                this.size[0] * 0.5,\n                this.size[1] * 0.5 + font_size * 0.3\n            );\n            ctx.textAlign = \"left\";\n        }\n    };\n\n    WidgetButton.prototype.onMouseDown = function(e, local_pos) {\n        if (\n            local_pos[0] > 1 &&\n            local_pos[1] > 1 &&\n            local_pos[0] < this.size[0] - 2 &&\n            local_pos[1] < this.size[1] - 2\n        ) {\n            this.clicked = true;\n            this.setOutputData(1, this.clicked);\n            this.triggerSlot(0, this.properties.message);\n            return true;\n        }\n    };\n\n    WidgetButton.prototype.onExecute = function() {\n        this.setOutputData(1, this.clicked);\n    };\n\n    WidgetButton.prototype.onMouseUp = function(e) {\n        this.clicked = false;\n    };\n\n    LiteGraph.registerNodeType(\"widget/button\", WidgetButton);\n\n    function WidgetToggle() {\n        this.addInput(\"\", \"boolean\");\n        this.addInput(\"e\", LiteGraph.ACTION);\n        this.addOutput(\"v\", \"boolean\");\n        this.addOutput(\"e\", LiteGraph.EVENT);\n        this.properties = { font: \"\", value: false };\n        this.size = [160, 44];\n    }\n\n    WidgetToggle.title = \"Toggle\";\n    WidgetToggle.desc = \"Toggles between true or false\";\n\n    WidgetToggle.prototype.onDrawForeground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        var size = this.size[1] * 0.5;\n        var margin = 0.25;\n        var h = this.size[1] * 0.8;\n        ctx.font = this.properties.font || (size * 0.8).toFixed(0) + \"px Arial\";\n        var w = ctx.measureText(this.title).width;\n        var x = (this.size[0] - (w + size)) * 0.5;\n\n        ctx.fillStyle = \"#AAA\";\n        ctx.fillRect(x, h - size, size, size);\n\n        ctx.fillStyle = this.properties.value ? \"#AEF\" : \"#000\";\n        ctx.fillRect(\n            x + size * margin,\n            h - size + size * margin,\n            size * (1 - margin * 2),\n            size * (1 - margin * 2)\n        );\n\n        ctx.textAlign = \"left\";\n        ctx.fillStyle = \"#AAA\";\n        ctx.fillText(this.title, size * 1.2 + x, h * 0.85);\n        ctx.textAlign = \"left\";\n    };\n\n    WidgetToggle.prototype.onAction = function(action) {\n        this.properties.value = !this.properties.value;\n        this.trigger(\"e\", this.properties.value);\n    };\n\n    WidgetToggle.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v != null) {\n            this.properties.value = v;\n        }\n        this.setOutputData(0, this.properties.value);\n    };\n\n    WidgetToggle.prototype.onMouseDown = function(e, local_pos) {\n        if (\n            local_pos[0] > 1 &&\n            local_pos[1] > 1 &&\n            local_pos[0] < this.size[0] - 2 &&\n            local_pos[1] < this.size[1] - 2\n        ) {\n            this.properties.value = !this.properties.value;\n            this.graph._version++;\n            this.trigger(\"e\", this.properties.value);\n            return true;\n        }\n    };\n\n    LiteGraph.registerNodeType(\"widget/toggle\", WidgetToggle);\n\n    /* Number ****************/\n\n    function WidgetNumber() {\n        this.addOutput(\"\", \"number\");\n        this.size = [80, 60];\n        this.properties = { min: -1000, max: 1000, value: 1, step: 1 };\n        this.old_y = -1;\n        this._remainder = 0;\n        this._precision = 0;\n        this.mouse_captured = false;\n    }\n\n    WidgetNumber.title = \"Number\";\n    WidgetNumber.desc = \"Widget to select number value\";\n\n    WidgetNumber.pixels_threshold = 10;\n    WidgetNumber.markers_color = \"#666\";\n\n    WidgetNumber.prototype.onDrawForeground = function(ctx) {\n        var x = this.size[0] * 0.5;\n        var h = this.size[1];\n        if (h > 30) {\n            ctx.fillStyle = WidgetNumber.markers_color;\n            ctx.beginPath();\n            ctx.moveTo(x, h * 0.1);\n            ctx.lineTo(x + h * 0.1, h * 0.2);\n            ctx.lineTo(x + h * -0.1, h * 0.2);\n            ctx.fill();\n            ctx.beginPath();\n            ctx.moveTo(x, h * 0.9);\n            ctx.lineTo(x + h * 0.1, h * 0.8);\n            ctx.lineTo(x + h * -0.1, h * 0.8);\n            ctx.fill();\n            ctx.font = (h * 0.7).toFixed(1) + \"px Arial\";\n        } else {\n            ctx.font = (h * 0.8).toFixed(1) + \"px Arial\";\n        }\n\n        ctx.textAlign = \"center\";\n        ctx.font = (h * 0.7).toFixed(1) + \"px Arial\";\n        ctx.fillStyle = \"#EEE\";\n        ctx.fillText(\n            this.properties.value.toFixed(this._precision),\n            x,\n            h * 0.75\n        );\n    };\n\n    WidgetNumber.prototype.onExecute = function() {\n        this.setOutputData(0, this.properties.value);\n    };\n\n    WidgetNumber.prototype.onPropertyChanged = function(name, value) {\n        var t = (this.properties.step + \"\").split(\".\");\n        this._precision = t.length > 1 ? t[1].length : 0;\n    };\n\n    WidgetNumber.prototype.onMouseDown = function(e, pos) {\n        if (pos[1] < 0) {\n            return;\n        }\n\n        this.old_y = e.canvasY;\n        this.captureInput(true);\n        this.mouse_captured = true;\n\n        return true;\n    };\n\n    WidgetNumber.prototype.onMouseMove = function(e) {\n        if (!this.mouse_captured) {\n            return;\n        }\n\n        var delta = this.old_y - e.canvasY;\n        if (e.shiftKey) {\n            delta *= 10;\n        }\n        if (e.metaKey || e.altKey) {\n            delta *= 0.1;\n        }\n        this.old_y = e.canvasY;\n\n        var steps = this._remainder + delta / WidgetNumber.pixels_threshold;\n        this._remainder = steps % 1;\n        steps = steps | 0;\n\n        var v = clamp(\n            this.properties.value + steps * this.properties.step,\n            this.properties.min,\n            this.properties.max\n        );\n        this.properties.value = v;\n        this.graph._version++;\n        this.setDirtyCanvas(true);\n    };\n\n    WidgetNumber.prototype.onMouseUp = function(e, pos) {\n        if (e.click_time < 200) {\n            var steps = pos[1] > this.size[1] * 0.5 ? -1 : 1;\n            this.properties.value = clamp(\n                this.properties.value + steps * this.properties.step,\n                this.properties.min,\n                this.properties.max\n            );\n            this.graph._version++;\n            this.setDirtyCanvas(true);\n        }\n\n        if (this.mouse_captured) {\n            this.mouse_captured = false;\n            this.captureInput(false);\n        }\n    };\n\n    LiteGraph.registerNodeType(\"widget/number\", WidgetNumber);\n\n\n    /* Combo ****************/\n\n    function WidgetCombo() {\n        this.addOutput(\"\", \"string\");\n        this.addOutput(\"change\", LiteGraph.EVENT);\n        this.size = [80, 60];\n        this.properties = { value: \"A\", values:\"A;B;C\" };\n        this.old_y = -1;\n        this.mouse_captured = false;\n\t\tthis._values = this.properties.values.split(\";\");\n\t\tvar that = this;\n        this.widgets_up = true;\n\t\tthis.widget = this.addWidget(\"combo\",\"\", this.properties.value, function(v){\n\t\t\tthat.properties.value = v;\n            that.triggerSlot(1, v);\n\t\t}, { property: \"value\", values: this._values } );\n    }\n\n    WidgetCombo.title = \"Combo\";\n    WidgetCombo.desc = \"Widget to select from a list\";\n\n    WidgetCombo.prototype.onExecute = function() {\n        this.setOutputData( 0, this.properties.value );\n    };\n\n    WidgetCombo.prototype.onPropertyChanged = function(name, value) {\n\t\tif(name == \"values\")\n\t\t{\n\t\t\tthis._values = value.split(\";\");\n\t\t\tthis.widget.options.values = this._values;\n\t\t}\n\t\telse if(name == \"value\")\n\t\t{\n\t\t\tthis.widget.value = value;\n\t\t}\n\t};\n\n    LiteGraph.registerNodeType(\"widget/combo\", WidgetCombo);\n\n\n    /* Knob ****************/\n\n    function WidgetKnob() {\n        this.addOutput(\"\", \"number\");\n        this.size = [64, 84];\n        this.properties = {\n            min: 0,\n            max: 1,\n            value: 0.5,\n            color: \"#7AF\",\n            precision: 2\n        };\n        this.value = -1;\n    }\n\n    WidgetKnob.title = \"Knob\";\n    WidgetKnob.desc = \"Circular controller\";\n    WidgetKnob.size = [80, 100];\n\n    WidgetKnob.prototype.onDrawForeground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        if (this.value == -1) {\n            this.value =\n                (this.properties.value - this.properties.min) /\n                (this.properties.max - this.properties.min);\n        }\n\n        var center_x = this.size[0] * 0.5;\n        var center_y = this.size[1] * 0.5;\n        var radius = Math.min(this.size[0], this.size[1]) * 0.5 - 5;\n        var w = Math.floor(radius * 0.05);\n\n        ctx.globalAlpha = 1;\n        ctx.save();\n        ctx.translate(center_x, center_y);\n        ctx.rotate(Math.PI * 0.75);\n\n        //bg\n        ctx.fillStyle = \"rgba(0,0,0,0.5)\";\n        ctx.beginPath();\n        ctx.moveTo(0, 0);\n        ctx.arc(0, 0, radius, 0, Math.PI * 1.5);\n        ctx.fill();\n\n        //value\n        ctx.strokeStyle = \"black\";\n        ctx.fillStyle = this.properties.color;\n        ctx.lineWidth = 2;\n        ctx.beginPath();\n        ctx.moveTo(0, 0);\n        ctx.arc(\n            0,\n            0,\n            radius - 4,\n            0,\n            Math.PI * 1.5 * Math.max(0.01, this.value)\n        );\n        ctx.closePath();\n        ctx.fill();\n        //ctx.stroke();\n        ctx.lineWidth = 1;\n        ctx.globalAlpha = 1;\n        ctx.restore();\n\n        //inner\n        ctx.fillStyle = \"black\";\n        ctx.beginPath();\n        ctx.arc(center_x, center_y, radius * 0.75, 0, Math.PI * 2, true);\n        ctx.fill();\n\n        //miniball\n        ctx.fillStyle = this.mouseOver ? \"white\" : this.properties.color;\n        ctx.beginPath();\n        var angle = this.value * Math.PI * 1.5 + Math.PI * 0.75;\n        ctx.arc(\n            center_x + Math.cos(angle) * radius * 0.65,\n            center_y + Math.sin(angle) * radius * 0.65,\n            radius * 0.05,\n            0,\n            Math.PI * 2,\n            true\n        );\n        ctx.fill();\n\n        //text\n        ctx.fillStyle = this.mouseOver ? \"white\" : \"#AAA\";\n        ctx.font = Math.floor(radius * 0.5) + \"px Arial\";\n        ctx.textAlign = \"center\";\n        ctx.fillText(\n            this.properties.value.toFixed(this.properties.precision),\n            center_x,\n            center_y + radius * 0.15\n        );\n    };\n\n    WidgetKnob.prototype.onExecute = function() {\n        this.setOutputData(0, this.properties.value);\n        this.boxcolor = LiteGraph.colorToString([\n            this.value,\n            this.value,\n            this.value\n        ]);\n    };\n\n    WidgetKnob.prototype.onMouseDown = function(e) {\n        this.center = [this.size[0] * 0.5, this.size[1] * 0.5 + 20];\n        this.radius = this.size[0] * 0.5;\n        if (\n            e.canvasY - this.pos[1] < 20 ||\n            LiteGraph.distance(\n                [e.canvasX, e.canvasY],\n                [this.pos[0] + this.center[0], this.pos[1] + this.center[1]]\n            ) > this.radius\n        ) {\n            return false;\n        }\n        this.oldmouse = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]];\n        this.captureInput(true);\n        return true;\n    };\n\n    WidgetKnob.prototype.onMouseMove = function(e) {\n        if (!this.oldmouse) {\n            return;\n        }\n\n        var m = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]];\n\n        var v = this.value;\n        v -= (m[1] - this.oldmouse[1]) * 0.01;\n        if (v > 1.0) {\n            v = 1.0;\n        } else if (v < 0.0) {\n            v = 0.0;\n        }\n        this.value = v;\n        this.properties.value =\n            this.properties.min +\n            (this.properties.max - this.properties.min) * this.value;\n        this.oldmouse = m;\n        this.setDirtyCanvas(true);\n    };\n\n    WidgetKnob.prototype.onMouseUp = function(e) {\n        if (this.oldmouse) {\n            this.oldmouse = null;\n            this.captureInput(false);\n        }\n    };\n\n    WidgetKnob.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"min\" || name == \"max\" || name == \"value\") {\n            this.properties[name] = parseFloat(value);\n            return true; //block\n        }\n    };\n\n    LiteGraph.registerNodeType(\"widget/knob\", WidgetKnob);\n\n    //Show value inside the debug console\n    function WidgetSliderGUI() {\n        this.addOutput(\"\", \"number\");\n        this.properties = {\n            value: 0.5,\n            min: 0,\n            max: 1,\n            text: \"V\"\n        };\n        var that = this;\n        this.size = [140, 40];\n        this.slider = this.addWidget(\n            \"slider\",\n            \"V\",\n            this.properties.value,\n            function(v) {\n                that.properties.value = v;\n            },\n            this.properties\n        );\n        this.widgets_up = true;\n    }\n\n    WidgetSliderGUI.title = \"Inner Slider\";\n\n    WidgetSliderGUI.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"value\") {\n            this.slider.value = value;\n        }\n    };\n\n    WidgetSliderGUI.prototype.onExecute = function() {\n        this.setOutputData(0, this.properties.value);\n    };\n\n    LiteGraph.registerNodeType(\"widget/internal_slider\", WidgetSliderGUI);\n\n    //Widget H SLIDER\n    function WidgetHSlider() {\n        this.size = [160, 26];\n        this.addOutput(\"\", \"number\");\n        this.properties = { color: \"#7AF\", min: 0, max: 1, value: 0.5 };\n        this.value = -1;\n    }\n\n    WidgetHSlider.title = \"H.Slider\";\n    WidgetHSlider.desc = \"Linear slider controller\";\n\n    WidgetHSlider.prototype.onDrawForeground = function(ctx) {\n        if (this.value == -1) {\n            this.value =\n                (this.properties.value - this.properties.min) /\n                (this.properties.max - this.properties.min);\n        }\n\n        //border\n        ctx.globalAlpha = 1;\n        ctx.lineWidth = 1;\n        ctx.fillStyle = \"#000\";\n        ctx.fillRect(2, 2, this.size[0] - 4, this.size[1] - 4);\n\n        ctx.fillStyle = this.properties.color;\n        ctx.beginPath();\n        ctx.rect(4, 4, (this.size[0] - 8) * this.value, this.size[1] - 8);\n        ctx.fill();\n    };\n\n    WidgetHSlider.prototype.onExecute = function() {\n        this.properties.value =\n            this.properties.min +\n            (this.properties.max - this.properties.min) * this.value;\n        this.setOutputData(0, this.properties.value);\n        this.boxcolor = LiteGraph.colorToString([\n            this.value,\n            this.value,\n            this.value\n        ]);\n    };\n\n    WidgetHSlider.prototype.onMouseDown = function(e) {\n        if (e.canvasY - this.pos[1] < 0) {\n            return false;\n        }\n\n        this.oldmouse = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]];\n        this.captureInput(true);\n        return true;\n    };\n\n    WidgetHSlider.prototype.onMouseMove = function(e) {\n        if (!this.oldmouse) {\n            return;\n        }\n\n        var m = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]];\n\n        var v = this.value;\n        var delta = m[0] - this.oldmouse[0];\n        v += delta / this.size[0];\n        if (v > 1.0) {\n            v = 1.0;\n        } else if (v < 0.0) {\n            v = 0.0;\n        }\n\n        this.value = v;\n\n        this.oldmouse = m;\n        this.setDirtyCanvas(true);\n    };\n\n    WidgetHSlider.prototype.onMouseUp = function(e) {\n        this.oldmouse = null;\n        this.captureInput(false);\n    };\n\n    WidgetHSlider.prototype.onMouseLeave = function(e) {\n        //this.oldmouse = null;\n    };\n\n    LiteGraph.registerNodeType(\"widget/hslider\", WidgetHSlider);\n\n    function WidgetProgress() {\n        this.size = [160, 26];\n        this.addInput(\"\", \"number\");\n        this.properties = { min: 0, max: 1, value: 0, color: \"#AAF\" };\n    }\n\n    WidgetProgress.title = \"Progress\";\n    WidgetProgress.desc = \"Shows data in linear progress\";\n\n    WidgetProgress.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v != undefined) {\n            this.properties[\"value\"] = v;\n        }\n    };\n\n    WidgetProgress.prototype.onDrawForeground = function(ctx) {\n        //border\n        ctx.lineWidth = 1;\n        ctx.fillStyle = this.properties.color;\n        var v =\n            (this.properties.value - this.properties.min) /\n            (this.properties.max - this.properties.min);\n        v = Math.min(1, v);\n        v = Math.max(0, v);\n        ctx.fillRect(2, 2, (this.size[0] - 4) * v, this.size[1] - 4);\n    };\n\n    LiteGraph.registerNodeType(\"widget/progress\", WidgetProgress);\n\n    function WidgetText() {\n        this.addInputs(\"\", 0);\n        this.properties = {\n            value: \"...\",\n            font: \"Arial\",\n            fontsize: 18,\n            color: \"#AAA\",\n            align: \"left\",\n            glowSize: 0,\n            decimals: 1\n        };\n    }\n\n    WidgetText.title = \"Text\";\n    WidgetText.desc = \"Shows the input value\";\n    WidgetText.widgets = [\n        { name: \"resize\", text: \"Resize box\", type: \"button\" },\n        { name: \"led_text\", text: \"LED\", type: \"minibutton\" },\n        { name: \"normal_text\", text: \"Normal\", type: \"minibutton\" }\n    ];\n\n    WidgetText.prototype.onDrawForeground = function(ctx) {\n        //ctx.fillStyle=\"#000\";\n        //ctx.fillRect(0,0,100,60);\n        ctx.fillStyle = this.properties[\"color\"];\n        var v = this.properties[\"value\"];\n\n        if (this.properties[\"glowSize\"]) {\n            ctx.shadowColor = this.properties.color;\n            ctx.shadowOffsetX = 0;\n            ctx.shadowOffsetY = 0;\n            ctx.shadowBlur = this.properties[\"glowSize\"];\n        } else {\n            ctx.shadowColor = \"transparent\";\n        }\n\n        var fontsize = this.properties[\"fontsize\"];\n\n        ctx.textAlign = this.properties[\"align\"];\n        ctx.font = fontsize.toString() + \"px \" + this.properties[\"font\"];\n        this.str =\n            typeof v == \"number\" ? v.toFixed(this.properties[\"decimals\"]) : v;\n\n        if (typeof this.str == \"string\") {\n            var lines = this.str.replace(/[\\r\\n]/g, \"\\\\n\").split(\"\\\\n\");\n            for (var i=0; i < lines.length; i++) {\n                ctx.fillText(\n                    lines[i],\n                    this.properties[\"align\"] == \"left\" ? 15 : this.size[0] - 15,\n                    fontsize * -0.15 + fontsize * (parseInt(i) + 1)\n                );\n            }\n        }\n\n        ctx.shadowColor = \"transparent\";\n        this.last_ctx = ctx;\n        ctx.textAlign = \"left\";\n    };\n\n    WidgetText.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v != null) {\n            this.properties[\"value\"] = v;\n        }\n        //this.setDirtyCanvas(true);\n    };\n\n    WidgetText.prototype.resize = function() {\n        if (!this.last_ctx) {\n            return;\n        }\n\n        var lines = this.str.split(\"\\\\n\");\n        this.last_ctx.font =\n            this.properties[\"fontsize\"] + \"px \" + this.properties[\"font\"];\n        var max = 0;\n        for (var i=0; i < lines.length; i++) {\n            var w = this.last_ctx.measureText(lines[i]).width;\n            if (max < w) {\n                max = w;\n            }\n        }\n        this.size[0] = max + 20;\n        this.size[1] = 4 + lines.length * this.properties[\"fontsize\"];\n\n        this.setDirtyCanvas(true);\n    };\n\n    WidgetText.prototype.onPropertyChanged = function(name, value) {\n        this.properties[name] = value;\n        this.str = typeof value == \"number\" ? value.toFixed(3) : value;\n        //this.resize();\n        return true;\n    };\n\n    LiteGraph.registerNodeType(\"widget/text\", WidgetText);\n\n    function WidgetPanel() {\n        this.size = [200, 100];\n        this.properties = {\n            borderColor: \"#ffffff\",\n            bgcolorTop: \"#f0f0f0\",\n            bgcolorBottom: \"#e0e0e0\",\n            shadowSize: 2,\n            borderRadius: 3\n        };\n    }\n\n    WidgetPanel.title = \"Panel\";\n    WidgetPanel.desc = \"Non interactive panel\";\n    WidgetPanel.widgets = [{ name: \"update\", text: \"Update\", type: \"button\" }];\n\n    WidgetPanel.prototype.createGradient = function(ctx) {\n        if (\n            this.properties[\"bgcolorTop\"] == \"\" ||\n            this.properties[\"bgcolorBottom\"] == \"\"\n        ) {\n            this.lineargradient = 0;\n            return;\n        }\n\n        this.lineargradient = ctx.createLinearGradient(0, 0, 0, this.size[1]);\n        this.lineargradient.addColorStop(0, this.properties[\"bgcolorTop\"]);\n        this.lineargradient.addColorStop(1, this.properties[\"bgcolorBottom\"]);\n    };\n\n    WidgetPanel.prototype.onDrawForeground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        if (this.lineargradient == null) {\n            this.createGradient(ctx);\n        }\n\n        if (!this.lineargradient) {\n            return;\n        }\n\n        ctx.lineWidth = 1;\n        ctx.strokeStyle = this.properties[\"borderColor\"];\n        //ctx.fillStyle = \"#ebebeb\";\n        ctx.fillStyle = this.lineargradient;\n\n        if (this.properties[\"shadowSize\"]) {\n            ctx.shadowColor = \"#000\";\n            ctx.shadowOffsetX = 0;\n            ctx.shadowOffsetY = 0;\n            ctx.shadowBlur = this.properties[\"shadowSize\"];\n        } else {\n            ctx.shadowColor = \"transparent\";\n        }\n\n        ctx.roundRect(\n            0,\n            0,\n            this.size[0] - 1,\n            this.size[1] - 1,\n            this.properties[\"shadowSize\"]\n        );\n        ctx.fill();\n        ctx.shadowColor = \"transparent\";\n        ctx.stroke();\n    };\n\n    LiteGraph.registerNodeType(\"widget/panel\", WidgetPanel);\n})(this);\n\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    function GamepadInput() {\r\n        this.addOutput(\"left_x_axis\", \"number\");\r\n        this.addOutput(\"left_y_axis\", \"number\");\r\n        this.addOutput(\"button_pressed\", LiteGraph.EVENT);\r\n        this.properties = { gamepad_index: 0, threshold: 0.1 };\r\n\r\n        this._left_axis = new Float32Array(2);\r\n        this._right_axis = new Float32Array(2);\r\n        this._triggers = new Float32Array(2);\r\n        this._previous_buttons = new Uint8Array(17);\r\n        this._current_buttons = new Uint8Array(17);\r\n    }\r\n\r\n    GamepadInput.title = \"Gamepad\";\r\n    GamepadInput.desc = \"gets the input of the gamepad\";\r\n\r\n    GamepadInput.CENTER = 0;\r\n    GamepadInput.LEFT = 1;\r\n    GamepadInput.RIGHT = 2;\r\n    GamepadInput.UP = 4;\r\n    GamepadInput.DOWN = 8;\r\n\r\n    GamepadInput.zero = new Float32Array(2);\r\n    GamepadInput.buttons = [\r\n        \"a\",\r\n        \"b\",\r\n        \"x\",\r\n        \"y\",\r\n        \"lb\",\r\n        \"rb\",\r\n        \"lt\",\r\n        \"rt\",\r\n        \"back\",\r\n        \"start\",\r\n        \"ls\",\r\n        \"rs\",\r\n        \"home\"\r\n    ];\r\n\r\n    GamepadInput.prototype.onExecute = function() {\r\n        //get gamepad\r\n        var gamepad = this.getGamepad();\r\n        var threshold = this.properties.threshold || 0.0;\r\n\r\n        if (gamepad) {\r\n            this._left_axis[0] =\r\n                Math.abs(gamepad.xbox.axes[\"lx\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"lx\"]\r\n                    : 0;\r\n            this._left_axis[1] =\r\n                Math.abs(gamepad.xbox.axes[\"ly\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"ly\"]\r\n                    : 0;\r\n            this._right_axis[0] =\r\n                Math.abs(gamepad.xbox.axes[\"rx\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"rx\"]\r\n                    : 0;\r\n            this._right_axis[1] =\r\n                Math.abs(gamepad.xbox.axes[\"ry\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"ry\"]\r\n                    : 0;\r\n            this._triggers[0] =\r\n                Math.abs(gamepad.xbox.axes[\"ltrigger\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"ltrigger\"]\r\n                    : 0;\r\n            this._triggers[1] =\r\n                Math.abs(gamepad.xbox.axes[\"rtrigger\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"rtrigger\"]\r\n                    : 0;\r\n        }\r\n\r\n        if (this.outputs) {\r\n            for (var i = 0; i < this.outputs.length; i++) {\r\n                var output = this.outputs[i];\r\n                if (!output.links || !output.links.length) {\r\n                    continue;\r\n                }\r\n                var v = null;\r\n\r\n                if (gamepad) {\r\n                    switch (output.name) {\r\n                        case \"left_axis\":\r\n                            v = this._left_axis;\r\n                            break;\r\n                        case \"right_axis\":\r\n                            v = this._right_axis;\r\n                            break;\r\n                        case \"left_x_axis\":\r\n                            v = this._left_axis[0];\r\n                            break;\r\n                        case \"left_y_axis\":\r\n                            v = this._left_axis[1];\r\n                            break;\r\n                        case \"right_x_axis\":\r\n                            v = this._right_axis[0];\r\n                            break;\r\n                        case \"right_y_axis\":\r\n                            v = this._right_axis[1];\r\n                            break;\r\n                        case \"trigger_left\":\r\n                            v = this._triggers[0];\r\n                            break;\r\n                        case \"trigger_right\":\r\n                            v = this._triggers[1];\r\n                            break;\r\n                        case \"a_button\":\r\n                            v = gamepad.xbox.buttons[\"a\"] ? 1 : 0;\r\n                            break;\r\n                        case \"b_button\":\r\n                            v = gamepad.xbox.buttons[\"b\"] ? 1 : 0;\r\n                            break;\r\n                        case \"x_button\":\r\n                            v = gamepad.xbox.buttons[\"x\"] ? 1 : 0;\r\n                            break;\r\n                        case \"y_button\":\r\n                            v = gamepad.xbox.buttons[\"y\"] ? 1 : 0;\r\n                            break;\r\n                        case \"lb_button\":\r\n                            v = gamepad.xbox.buttons[\"lb\"] ? 1 : 0;\r\n                            break;\r\n                        case \"rb_button\":\r\n                            v = gamepad.xbox.buttons[\"rb\"] ? 1 : 0;\r\n                            break;\r\n                        case \"ls_button\":\r\n                            v = gamepad.xbox.buttons[\"ls\"] ? 1 : 0;\r\n                            break;\r\n                        case \"rs_button\":\r\n                            v = gamepad.xbox.buttons[\"rs\"] ? 1 : 0;\r\n                            break;\r\n                        case \"hat_left\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.LEFT;\r\n                            break;\r\n                        case \"hat_right\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.RIGHT;\r\n                            break;\r\n                        case \"hat_up\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.UP;\r\n                            break;\r\n                        case \"hat_down\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.DOWN;\r\n                            break;\r\n                        case \"hat\":\r\n                            v = gamepad.xbox.hatmap;\r\n                            break;\r\n                        case \"start_button\":\r\n                            v = gamepad.xbox.buttons[\"start\"] ? 1 : 0;\r\n                            break;\r\n                        case \"back_button\":\r\n                            v = gamepad.xbox.buttons[\"back\"] ? 1 : 0;\r\n                            break;\r\n                        case \"button_pressed\":\r\n                            for (\r\n                                var j = 0;\r\n                                j < this._current_buttons.length;\r\n                                ++j\r\n                            ) {\r\n                                if (\r\n                                    this._current_buttons[j] &&\r\n                                    !this._previous_buttons[j]\r\n                                ) {\r\n                                    this.triggerSlot(\r\n                                        i,\r\n                                        GamepadInput.buttons[j]\r\n                                    );\r\n                                }\r\n                            }\r\n                            break;\r\n                        default:\r\n                            break;\r\n                    }\r\n                } else {\r\n                    //if no gamepad is connected, output 0\r\n                    switch (output.name) {\r\n                        case \"button_pressed\":\r\n                            break;\r\n                        case \"left_axis\":\r\n                        case \"right_axis\":\r\n                            v = GamepadInput.zero;\r\n                            break;\r\n                        default:\r\n                            v = 0;\r\n                    }\r\n                }\r\n                this.setOutputData(i, v);\r\n            }\r\n        }\r\n    };\r\n\r\n\tGamepadInput.mapping = {a:0,b:1,x:2,y:3,lb:4,rb:5,lt:6,rt:7,back:8,start:9,ls:10,rs:11 };\r\n\tGamepadInput.mapping_array = [\"a\",\"b\",\"x\",\"y\",\"lb\",\"rb\",\"lt\",\"rt\",\"back\",\"start\",\"ls\",\"rs\"];\r\n\r\n    GamepadInput.prototype.getGamepad = function() {\r\n        var getGamepads =\r\n            navigator.getGamepads ||\r\n            navigator.webkitGetGamepads ||\r\n            navigator.mozGetGamepads;\r\n        if (!getGamepads) {\r\n            return null;\r\n        }\r\n        var gamepads = getGamepads.call(navigator);\r\n        var gamepad = null;\r\n\r\n        this._previous_buttons.set(this._current_buttons);\r\n\r\n        //pick the first connected\r\n        for (var i = this.properties.gamepad_index; i < 4; i++) {\r\n            if (!gamepads[i]) {\r\n                continue;\r\n            }\r\n            gamepad = gamepads[i];\r\n\r\n            //xbox controller mapping\r\n            var xbox = this.xbox_mapping;\r\n            if (!xbox) {\r\n                xbox = this.xbox_mapping = {\r\n                    axes: [],\r\n                    buttons: {},\r\n                    hat: \"\",\r\n                    hatmap: GamepadInput.CENTER\r\n                };\r\n            }\r\n\r\n            xbox.axes[\"lx\"] = gamepad.axes[0];\r\n            xbox.axes[\"ly\"] = gamepad.axes[1];\r\n            xbox.axes[\"rx\"] = gamepad.axes[2];\r\n            xbox.axes[\"ry\"] = gamepad.axes[3];\r\n            xbox.axes[\"ltrigger\"] = gamepad.buttons[6].value;\r\n            xbox.axes[\"rtrigger\"] = gamepad.buttons[7].value;\r\n            xbox.hat = \"\";\r\n            xbox.hatmap = GamepadInput.CENTER;\r\n\r\n            for (var j = 0; j < gamepad.buttons.length; j++) {\r\n                this._current_buttons[j] = gamepad.buttons[j].pressed;\r\n\r\n\t\t\t\tif(j < 12)\r\n\t\t\t\t{\r\n\t\t\t\t\txbox.buttons[ GamepadInput.mapping_array[j] ] = gamepad.buttons[j].pressed;\r\n\t\t\t\t\tif(gamepad.buttons[j].was_pressed)\r\n\t\t\t\t\t\tthis.trigger( GamepadInput.mapping_array[j] + \"_button_event\" );\r\n\t\t\t\t}\r\n\t\t\t\telse //mapping of XBOX\r\n\t\t\t\t\tswitch ( j ) //I use a switch to ensure that a player with another gamepad could play\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcase 12:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"up\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.UP;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 13:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"down\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.DOWN;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 14:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"left\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.LEFT;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 15:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"right\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.RIGHT;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 16:\r\n\t\t\t\t\t\t\txbox.buttons[\"home\"] = gamepad.buttons[j].pressed;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tdefault:\r\n\t\t\t\t\t}\r\n            }\r\n            gamepad.xbox = xbox;\r\n            return gamepad;\r\n        }\r\n    };\r\n\r\n    GamepadInput.prototype.onDrawBackground = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        //render gamepad state?\r\n        var la = this._left_axis;\r\n        var ra = this._right_axis;\r\n        ctx.strokeStyle = \"#88A\";\r\n        ctx.strokeRect(\r\n            (la[0] + 1) * 0.5 * this.size[0] - 4,\r\n            (la[1] + 1) * 0.5 * this.size[1] - 4,\r\n            8,\r\n            8\r\n        );\r\n        ctx.strokeStyle = \"#8A8\";\r\n        ctx.strokeRect(\r\n            (ra[0] + 1) * 0.5 * this.size[0] - 4,\r\n            (ra[1] + 1) * 0.5 * this.size[1] - 4,\r\n            8,\r\n            8\r\n        );\r\n        var h = this.size[1] / this._current_buttons.length;\r\n        ctx.fillStyle = \"#AEB\";\r\n        for (var i = 0; i < this._current_buttons.length; ++i) {\r\n            if (this._current_buttons[i]) {\r\n                ctx.fillRect(0, h * i, 6, h);\r\n            }\r\n        }\r\n    };\r\n\r\n    GamepadInput.prototype.onGetOutputs = function() {\r\n        return [\r\n            [\"left_axis\", \"vec2\"],\r\n            [\"right_axis\", \"vec2\"],\r\n            [\"left_x_axis\", \"number\"],\r\n            [\"left_y_axis\", \"number\"],\r\n            [\"right_x_axis\", \"number\"],\r\n            [\"right_y_axis\", \"number\"],\r\n            [\"trigger_left\", \"number\"],\r\n            [\"trigger_right\", \"number\"],\r\n            [\"a_button\", \"number\"],\r\n            [\"b_button\", \"number\"],\r\n            [\"x_button\", \"number\"],\r\n            [\"y_button\", \"number\"],\r\n            [\"lb_button\", \"number\"],\r\n            [\"rb_button\", \"number\"],\r\n            [\"ls_button\", \"number\"],\r\n            [\"rs_button\", \"number\"],\r\n            [\"start_button\", \"number\"],\r\n            [\"back_button\", \"number\"],\r\n            [\"a_button_event\", LiteGraph.EVENT ],\r\n            [\"b_button_event\", LiteGraph.EVENT ],\r\n            [\"x_button_event\", LiteGraph.EVENT ],\r\n            [\"y_button_event\", LiteGraph.EVENT ],\r\n            [\"lb_button_event\", LiteGraph.EVENT ],\r\n            [\"rb_button_event\", LiteGraph.EVENT ],\r\n            [\"ls_button_event\", LiteGraph.EVENT ],\r\n            [\"rs_button_event\", LiteGraph.EVENT ],\r\n            [\"start_button_event\", LiteGraph.EVENT ],\r\n            [\"back_button_event\", LiteGraph.EVENT ],\r\n            [\"hat_left\", \"number\"],\r\n            [\"hat_right\", \"number\"],\r\n            [\"hat_up\", \"number\"],\r\n            [\"hat_down\", \"number\"],\r\n            [\"hat\", \"number\"],\r\n            [\"button_pressed\", LiteGraph.EVENT]\r\n        ];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"input/gamepad\", GamepadInput);\r\n\r\n})(this);\r\n\n(function(global) {\n    var LiteGraph = global.LiteGraph;\n\n    //Converter\n    function Converter() {\n        this.addInput(\"in\", 0);\n\t\tthis.addOutput(\"out\", 0);\n        this.size = [80, 30];\n    }\n\n    Converter.title = \"Converter\";\n    Converter.desc = \"type A to type B\";\n\n    Converter.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        if (this.outputs) {\n            for (var i = 0; i < this.outputs.length; i++) {\n                var output = this.outputs[i];\n                if (!output.links || !output.links.length) {\n                    continue;\n                }\n\n                var result = null;\n                switch (output.name) {\n                    case \"number\":\n                        result = v.length ? v[0] : parseFloat(v);\n                        break;\n                    case \"vec2\":\n                    case \"vec3\":\n                    case \"vec4\":\n                        var result = null;\n                        var count = 1;\n                        switch (output.name) {\n                            case \"vec2\":\n                                count = 2;\n                                break;\n                            case \"vec3\":\n                                count = 3;\n                                break;\n                            case \"vec4\":\n                                count = 4;\n                                break;\n                        }\n\n                        var result = new Float32Array(count);\n                        if (v.length) {\n                            for (\n                                var j = 0;\n                                j < v.length && j < result.length;\n                                j++\n                            ) {\n                                result[j] = v[j];\n                            }\n                        } else {\n                            result[0] = parseFloat(v);\n                        }\n                        break;\n                }\n                this.setOutputData(i, result);\n            }\n        }\n    };\n\n    Converter.prototype.onGetOutputs = function() {\n        return [\n            [\"number\", \"number\"],\n            [\"vec2\", \"vec2\"],\n            [\"vec3\", \"vec3\"],\n            [\"vec4\", \"vec4\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/converter\", Converter);\n\n    //Bypass\n    function Bypass() {\n        this.addInput(\"in\");\n        this.addOutput(\"out\");\n        this.size = [80, 30];\n    }\n\n    Bypass.title = \"Bypass\";\n    Bypass.desc = \"removes the type\";\n\n    Bypass.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        this.setOutputData(0, v);\n    };\n\n    LiteGraph.registerNodeType(\"math/bypass\", Bypass);\n\n    function ToNumber() {\n        this.addInput(\"in\");\n        this.addOutput(\"out\");\n    }\n\n    ToNumber.title = \"to Number\";\n    ToNumber.desc = \"Cast to number\";\n\n    ToNumber.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        this.setOutputData(0, Number(v));\n    };\n\n    LiteGraph.registerNodeType(\"math/to_number\", ToNumber);\n\n    function MathRange() {\n        this.addInput(\"in\", \"number\", { locked: true });\n        this.addOutput(\"out\", \"number\", { locked: true });\n        this.addOutput(\"clamped\", \"number\", { locked: true });\n\n        this.addProperty(\"in\", 0);\n        this.addProperty(\"in_min\", 0);\n        this.addProperty(\"in_max\", 1);\n        this.addProperty(\"out_min\", 0);\n        this.addProperty(\"out_max\", 1);\n\n        this.size = [120, 50];\n    }\n\n    MathRange.title = \"Range\";\n    MathRange.desc = \"Convert a number from one range to another\";\n\n    MathRange.prototype.getTitle = function() {\n        if (this.flags.collapsed) {\n            return (this._last_v || 0).toFixed(2);\n        }\n        return this.title;\n    };\n\n    MathRange.prototype.onExecute = function() {\n        if (this.inputs) {\n            for (var i = 0; i < this.inputs.length; i++) {\n                var input = this.inputs[i];\n                var v = this.getInputData(i);\n                if (v === undefined) {\n                    continue;\n                }\n                this.properties[input.name] = v;\n            }\n        }\n\n        var v = this.properties[\"in\"];\n        if (v === undefined || v === null || v.constructor !== Number) {\n            v = 0;\n        }\n\n        var in_min = this.properties.in_min;\n        var in_max = this.properties.in_max;\n        var out_min = this.properties.out_min;\n        var out_max = this.properties.out_max;\n\t\t/*\n\t\tif( in_min > in_max )\n\t\t{\n\t\t\tin_min = in_max;\n\t\t\tin_max = this.properties.in_min;\n\t\t}\n\t\tif( out_min > out_max )\n\t\t{\n\t\t\tout_min = out_max;\n\t\t\tout_max = this.properties.out_min;\n\t\t}\n\t\t*/\n\n        this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min;\n        this.setOutputData(0, this._last_v);\n        this.setOutputData(1, clamp( this._last_v, out_min, out_max ));\n    };\n\n    MathRange.prototype.onDrawBackground = function(ctx) {\n        //show the current value\n        if (this._last_v) {\n            this.outputs[0].label = this._last_v.toFixed(3);\n        } else {\n            this.outputs[0].label = \"?\";\n        }\n    };\n\n    MathRange.prototype.onGetInputs = function() {\n        return [\n            [\"in_min\", \"number\"],\n            [\"in_max\", \"number\"],\n            [\"out_min\", \"number\"],\n            [\"out_max\", \"number\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/range\", MathRange);\n\n    function MathRand() {\n        this.addOutput(\"value\", \"number\");\n        this.addProperty(\"min\", 0);\n        this.addProperty(\"max\", 1);\n        this.size = [80, 30];\n    }\n\n    MathRand.title = \"Rand\";\n    MathRand.desc = \"Random number\";\n\n    MathRand.prototype.onExecute = function() {\n        if (this.inputs) {\n            for (var i = 0; i < this.inputs.length; i++) {\n                var input = this.inputs[i];\n                var v = this.getInputData(i);\n                if (v === undefined) {\n                    continue;\n                }\n                this.properties[input.name] = v;\n            }\n        }\n\n        var min = this.properties.min;\n        var max = this.properties.max;\n        this._last_v = Math.random() * (max - min) + min;\n        this.setOutputData(0, this._last_v);\n    };\n\n    MathRand.prototype.onDrawBackground = function(ctx) {\n        //show the current value\n        this.outputs[0].label = (this._last_v || 0).toFixed(3);\n    };\n\n    MathRand.prototype.onGetInputs = function() {\n        return [[\"min\", \"number\"], [\"max\", \"number\"]];\n    };\n\n    LiteGraph.registerNodeType(\"math/rand\", MathRand);\n\n    //basic continuous noise\n    function MathNoise() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.addProperty(\"min\", 0);\n        this.addProperty(\"max\", 1);\n        this.addProperty(\"smooth\", true);\n        this.addProperty(\"seed\", 0);\n        this.addProperty(\"octaves\", 1);\n        this.addProperty(\"persistence\", 0.8);\n        this.addProperty(\"speed\", 1);\n        this.size = [90, 30];\n    }\n\n    MathNoise.title = \"Noise\";\n    MathNoise.desc = \"Random number with temporal continuity\";\n    MathNoise.data = null;\n\n    MathNoise.getValue = function(f, smooth) {\n        if (!MathNoise.data) {\n            MathNoise.data = new Float32Array(1024);\n            for (var i = 0; i < MathNoise.data.length; ++i) {\n                MathNoise.data[i] = Math.random();\n            }\n        }\n        f = f % 1024;\n        if (f < 0) {\n            f += 1024;\n        }\n        var f_min = Math.floor(f);\n        var f = f - f_min;\n        var r1 = MathNoise.data[f_min];\n        var r2 = MathNoise.data[f_min == 1023 ? 0 : f_min + 1];\n        if (smooth) {\n            f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n        }\n        return r1 * (1 - f) + r2 * f;\n    };\n\n    MathNoise.prototype.onExecute = function() {\n        var f = this.getInputData(0) || 0;\n\t\tvar iterations = this.properties.octaves || 1;\n\t\tvar r = 0;\n\t\tvar amp = 1;\n\t\tvar seed = this.properties.seed || 0;\n\t\tf += seed;\n\t\tvar speed = this.properties.speed || 1;\n\t\tvar total_amp = 0;\n\t\tfor(var i = 0; i < iterations; ++i)\n\t\t{\n\t\t\tr += MathNoise.getValue(f * (1+i) * speed, this.properties.smooth) * amp;\n\t\t\ttotal_amp += amp;\n\t\t\tamp *= this.properties.persistence;\n\t\t\tif(amp < 0.001)\n\t\t\t\tbreak;\n\t\t}\n\t\tr /= total_amp;\n        var min = this.properties.min;\n        var max = this.properties.max;\n        this._last_v = r * (max - min) + min;\n        this.setOutputData(0, this._last_v);\n    };\n\n    MathNoise.prototype.onDrawBackground = function(ctx) {\n        //show the current value\n        this.outputs[0].label = (this._last_v || 0).toFixed(3);\n    };\n\n    LiteGraph.registerNodeType(\"math/noise\", MathNoise);\n\n    //generates spikes every random time\n    function MathSpikes() {\n        this.addOutput(\"out\", \"number\");\n        this.addProperty(\"min_time\", 1);\n        this.addProperty(\"max_time\", 2);\n        this.addProperty(\"duration\", 0.2);\n        this.size = [90, 30];\n        this._remaining_time = 0;\n        this._blink_time = 0;\n    }\n\n    MathSpikes.title = \"Spikes\";\n    MathSpikes.desc = \"spike every random time\";\n\n    MathSpikes.prototype.onExecute = function() {\n        var dt = this.graph.elapsed_time; //in secs\n\n        this._remaining_time -= dt;\n        this._blink_time -= dt;\n\n        var v = 0;\n        if (this._blink_time > 0) {\n            var f = this._blink_time / this.properties.duration;\n            v = 1 / (Math.pow(f * 8 - 4, 4) + 1);\n        }\n\n        if (this._remaining_time < 0) {\n            this._remaining_time =\n                Math.random() *\n                    (this.properties.max_time - this.properties.min_time) +\n                this.properties.min_time;\n            this._blink_time = this.properties.duration;\n            this.boxcolor = \"#FFF\";\n        } else {\n            this.boxcolor = \"#000\";\n        }\n        this.setOutputData(0, v);\n    };\n\n    LiteGraph.registerNodeType(\"math/spikes\", MathSpikes);\n\n    //Math clamp\n    function MathClamp() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n        this.addProperty(\"min\", 0);\n        this.addProperty(\"max\", 1);\n    }\n\n    MathClamp.title = \"Clamp\";\n    MathClamp.desc = \"Clamp number between min and max\";\n    //MathClamp.filter = \"shader\";\n\n    MathClamp.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        v = Math.max(this.properties.min, v);\n        v = Math.min(this.properties.max, v);\n        this.setOutputData(0, v);\n    };\n\n    MathClamp.prototype.getCode = function(lang) {\n        var code = \"\";\n        if (this.isInputConnected(0)) {\n            code +=\n                \"clamp({{0}},\" +\n                this.properties.min +\n                \",\" +\n                this.properties.max +\n                \")\";\n        }\n        return code;\n    };\n\n    LiteGraph.registerNodeType(\"math/clamp\", MathClamp);\n\n    //Math ABS\n    function MathLerp() {\n        this.properties = { f: 0.5 };\n        this.addInput(\"A\", \"number\");\n        this.addInput(\"B\", \"number\");\n\n        this.addOutput(\"out\", \"number\");\n    }\n\n    MathLerp.title = \"Lerp\";\n    MathLerp.desc = \"Linear Interpolation\";\n\n    MathLerp.prototype.onExecute = function() {\n        var v1 = this.getInputData(0);\n        if (v1 == null) {\n            v1 = 0;\n        }\n        var v2 = this.getInputData(1);\n        if (v2 == null) {\n            v2 = 0;\n        }\n\n        var f = this.properties.f;\n\n        var _f = this.getInputData(2);\n        if (_f !== undefined) {\n            f = _f;\n        }\n\n        this.setOutputData(0, v1 * (1 - f) + v2 * f);\n    };\n\n    MathLerp.prototype.onGetInputs = function() {\n        return [[\"f\", \"number\"]];\n    };\n\n    LiteGraph.registerNodeType(\"math/lerp\", MathLerp);\n\n    //Math ABS\n    function MathAbs() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n    }\n\n    MathAbs.title = \"Abs\";\n    MathAbs.desc = \"Absolute\";\n\n    MathAbs.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        this.setOutputData(0, Math.abs(v));\n    };\n\n    LiteGraph.registerNodeType(\"math/abs\", MathAbs);\n\n    //Math Floor\n    function MathFloor() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n    }\n\n    MathFloor.title = \"Floor\";\n    MathFloor.desc = \"Floor number to remove fractional part\";\n\n    MathFloor.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        this.setOutputData(0, Math.floor(v));\n    };\n\n    LiteGraph.registerNodeType(\"math/floor\", MathFloor);\n\n    //Math frac\n    function MathFrac() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n    }\n\n    MathFrac.title = \"Frac\";\n    MathFrac.desc = \"Returns fractional part\";\n\n    MathFrac.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        this.setOutputData(0, v % 1);\n    };\n\n    LiteGraph.registerNodeType(\"math/frac\", MathFrac);\n\n    //Math Floor\n    function MathSmoothStep() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n        this.properties = { A: 0, B: 1 };\n    }\n\n    MathSmoothStep.title = \"Smoothstep\";\n    MathSmoothStep.desc = \"Smoothstep\";\n\n    MathSmoothStep.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v === undefined) {\n            return;\n        }\n\n        var edge0 = this.properties.A;\n        var edge1 = this.properties.B;\n\n        // Scale, bias and saturate x to 0..1 range\n        v = clamp((v - edge0) / (edge1 - edge0), 0.0, 1.0);\n        // Evaluate polynomial\n        v = v * v * (3 - 2 * v);\n\n        this.setOutputData(0, v);\n    };\n\n    LiteGraph.registerNodeType(\"math/smoothstep\", MathSmoothStep);\n\n    //Math scale\n    function MathScale() {\n        this.addInput(\"in\", \"number\", { label: \"\" });\n        this.addOutput(\"out\", \"number\", { label: \"\" });\n        this.size = [80, 30];\n        this.addProperty(\"factor\", 1);\n    }\n\n    MathScale.title = \"Scale\";\n    MathScale.desc = \"v * factor\";\n\n    MathScale.prototype.onExecute = function() {\n        var value = this.getInputData(0);\n        if (value != null) {\n            this.setOutputData(0, value * this.properties.factor);\n        }\n    };\n\n    LiteGraph.registerNodeType(\"math/scale\", MathScale);\n\n\t//Gate\n\tfunction Gate() {\n\t\tthis.addInput(\"v\",\"boolean\");\n\t\tthis.addInput(\"A\");\n\t\tthis.addInput(\"B\");\n\t\tthis.addOutput(\"out\");\n\t}\n\n\tGate.title = \"Gate\";\n\tGate.desc = \"if v is true, then outputs A, otherwise B\";\n\n\tGate.prototype.onExecute = function() {\n\t\tvar v = this.getInputData(0);\n\t\tthis.setOutputData(0, this.getInputData( v ? 1 : 2 ));\n\t};\n\n\tLiteGraph.registerNodeType(\"math/gate\", Gate);\n\n\n    //Math Average\n    function MathAverageFilter() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n        this.addProperty(\"samples\", 10);\n        this._values = new Float32Array(10);\n        this._current = 0;\n    }\n\n    MathAverageFilter.title = \"Average\";\n    MathAverageFilter.desc = \"Average Filter\";\n\n    MathAverageFilter.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            v = 0;\n        }\n\n        var num_samples = this._values.length;\n\n        this._values[this._current % num_samples] = v;\n        this._current += 1;\n        if (this._current > num_samples) {\n            this._current = 0;\n        }\n\n        var avr = 0;\n        for (var i = 0; i < num_samples; ++i) {\n            avr += this._values[i];\n        }\n\n        this.setOutputData(0, avr / num_samples);\n    };\n\n    MathAverageFilter.prototype.onPropertyChanged = function(name, value) {\n        if (value < 1) {\n            value = 1;\n        }\n        this.properties.samples = Math.round(value);\n        var old = this._values;\n\n        this._values = new Float32Array(this.properties.samples);\n        if (old.length <= this._values.length) {\n            this._values.set(old);\n        } else {\n            this._values.set(old.subarray(0, this._values.length));\n        }\n    };\n\n    LiteGraph.registerNodeType(\"math/average\", MathAverageFilter);\n\n    //Math\n    function MathTendTo() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.addProperty(\"factor\", 0.1);\n        this.size = [80, 30];\n        this._value = null;\n    }\n\n    MathTendTo.title = \"TendTo\";\n    MathTendTo.desc = \"moves the output value always closer to the input\";\n\n    MathTendTo.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            v = 0;\n        }\n        var f = this.properties.factor;\n        if (this._value == null) {\n            this._value = v;\n        } else {\n            this._value = this._value * (1 - f) + v * f;\n        }\n        this.setOutputData(0, this._value);\n    };\n\n    LiteGraph.registerNodeType(\"math/tendTo\", MathTendTo);\n\n    //Math operation\n    function MathOperation() {\n        this.addInput(\"A\", \"number,array,object\");\n        this.addInput(\"B\", \"number\");\n        this.addOutput(\"=\", \"number\");\n        this.addProperty(\"A\", 1);\n        this.addProperty(\"B\", 1);\n        this.addProperty(\"OP\", \"+\", \"enum\", { values: MathOperation.values });\n\t\tthis._func = MathOperation.funcs[this.properties.OP];\n\t\tthis._result = []; //only used for arrays\n    }\n\n    MathOperation.values = [\"+\", \"-\", \"*\", \"/\", \"%\", \"^\", \"max\", \"min\"];\n    MathOperation.funcs = {\n        \"+\": function(A,B) { return A + B; },\n        \"-\": function(A,B) { return A - B; },\n        \"x\": function(A,B) { return A * B; },\n        \"X\": function(A,B) { return A * B; },\n        \"*\": function(A,B) { return A * B; },\n        \"/\": function(A,B) { return A / B; },\n        \"%\": function(A,B) { return A % B; },\n        \"^\": function(A,B) { return Math.pow(A, B); },\n        \"max\": function(A,B) { return Math.max(A, B); },\n        \"min\": function(A,B) { return Math.min(A, B); }\n    };\n\n\tMathOperation.title = \"Operation\";\n    MathOperation.desc = \"Easy math operators\";\n    MathOperation[\"@OP\"] = {\n        type: \"enum\",\n        title: \"operation\",\n        values: MathOperation.values\n    };\n    MathOperation.size = [100, 60];\n\n    MathOperation.prototype.getTitle = function() {\n\t\tif(this.properties.OP == \"max\" || this.properties.OP == \"min\")\n\t\t\treturn this.properties.OP + \"(A,B)\";\n        return \"A \" + this.properties.OP + \" B\";\n    };\n\n    MathOperation.prototype.setValue = function(v) {\n        if (typeof v == \"string\") {\n            v = parseFloat(v);\n        }\n        this.properties[\"value\"] = v;\n    };\n\n    MathOperation.prototype.onPropertyChanged = function(name, value)\n\t{\n\t\tif (name != \"OP\")\n\t\t\treturn;\n        this._func = MathOperation.funcs[this.properties.OP];\n        if(!this._func)\n        {\n            console.warn(\"Unknown operation: \" + this.properties.OP);\n            this._func = function(A) { return A; };\n        }\n\t}\n\n    MathOperation.prototype.onExecute = function() {\n        var A = this.getInputData(0);\n        var B = this.getInputData(1);\n        if ( A != null ) {\n\t\t\tif( A.constructor === Number )\n\t            this.properties[\"A\"] = A;\n        } else {\n            A = this.properties[\"A\"];\n        }\n\n        if (B != null) {\n            this.properties[\"B\"] = B;\n        } else {\n            B = this.properties[\"B\"];\n        }\n\n        var func = MathOperation.funcs[this.properties.OP];\n\n\t\tvar result;\n\t\tif(A.constructor === Number)\n\t\t{\n\t        result = 0;\n\t\t\tresult = func(A,B);\n\t\t}\n\t\telse if(A.constructor === Array)\n\t\t{\n\t\t\tresult = this._result;\n\t\t\tresult.length = A.length;\n\t\t\tfor(var i = 0; i < A.length; ++i)\n\t\t\t\tresult[i] = func(A[i],B);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tresult = {};\n\t\t\tfor(var i in A)\n\t\t\t\tresult[i] = func(A[i],B);\n\t\t}\n\t    this.setOutputData(0, result);\n    };\n\n    MathOperation.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        ctx.font = \"40px Arial\";\n        ctx.fillStyle = \"#666\";\n        ctx.textAlign = \"center\";\n        ctx.fillText(\n            this.properties.OP,\n            this.size[0] * 0.5,\n            (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5\n        );\n        ctx.textAlign = \"left\";\n    };\n\n    LiteGraph.registerNodeType(\"math/operation\", MathOperation);\n\n    LiteGraph.registerSearchboxExtra(\"math/operation\", \"MAX\", {\n        properties: {OP:\"max\"},\n        title: \"MAX()\"\n    });\n\n    LiteGraph.registerSearchboxExtra(\"math/operation\", \"MIN\", {\n        properties: {OP:\"min\"},\n        title: \"MIN()\"\n    });\n\n\n    //Math compare\n    function MathCompare() {\n        this.addInput(\"A\", \"number\");\n        this.addInput(\"B\", \"number\");\n        this.addOutput(\"A==B\", \"boolean\");\n        this.addOutput(\"A!=B\", \"boolean\");\n        this.addProperty(\"A\", 0);\n        this.addProperty(\"B\", 0);\n    }\n\n    MathCompare.title = \"Compare\";\n    MathCompare.desc = \"compares between two values\";\n\n    MathCompare.prototype.onExecute = function() {\n        var A = this.getInputData(0);\n        var B = this.getInputData(1);\n        if (A !== undefined) {\n            this.properties[\"A\"] = A;\n        } else {\n            A = this.properties[\"A\"];\n        }\n\n        if (B !== undefined) {\n            this.properties[\"B\"] = B;\n        } else {\n            B = this.properties[\"B\"];\n        }\n\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            var output = this.outputs[i];\n            if (!output.links || !output.links.length) {\n                continue;\n            }\n            var value;\n            switch (output.name) {\n                case \"A==B\":\n                    value = A == B;\n                    break;\n                case \"A!=B\":\n                    value = A != B;\n                    break;\n                case \"A>B\":\n                    value = A > B;\n                    break;\n                case \"A<B\":\n                    value = A < B;\n                    break;\n                case \"A<=B\":\n                    value = A <= B;\n                    break;\n                case \"A>=B\":\n                    value = A >= B;\n                    break;\n            }\n            this.setOutputData(i, value);\n        }\n    };\n\n    MathCompare.prototype.onGetOutputs = function() {\n        return [\n            [\"A==B\", \"boolean\"],\n            [\"A!=B\", \"boolean\"],\n            [\"A>B\", \"boolean\"],\n            [\"A<B\", \"boolean\"],\n            [\"A>=B\", \"boolean\"],\n            [\"A<=B\", \"boolean\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/compare\", MathCompare);\n\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"==\", {\n        outputs: [[\"A==B\", \"boolean\"]],\n        title: \"A==B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"!=\", {\n        outputs: [[\"A!=B\", \"boolean\"]],\n        title: \"A!=B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \">\", {\n        outputs: [[\"A>B\", \"boolean\"]],\n        title: \"A>B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"<\", {\n        outputs: [[\"A<B\", \"boolean\"]],\n        title: \"A<B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \">=\", {\n        outputs: [[\"A>=B\", \"boolean\"]],\n        title: \"A>=B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"<=\", {\n        outputs: [[\"A<=B\", \"boolean\"]],\n        title: \"A<=B\"\n    });\n\n    function MathCondition() {\n        this.addInput(\"A\", \"number\");\n        this.addInput(\"B\", \"number\");\n        this.addOutput(\"true\", \"boolean\");\n        this.addOutput(\"false\", \"boolean\");\n        this.addProperty(\"A\", 1);\n        this.addProperty(\"B\", 1);\n        this.addProperty(\"OP\", \">\", \"enum\", { values: MathCondition.values });\n\t\tthis.addWidget(\"combo\",\"Cond.\",this.properties.OP,{ property: \"OP\", values: MathCondition.values } );\n\n        this.size = [80, 60];\n    }\n\n    MathCondition.values = [\">\", \"<\", \"==\", \"!=\", \"<=\", \">=\", \"||\", \"&&\" ];\n    MathCondition[\"@OP\"] = {\n        type: \"enum\",\n        title: \"operation\",\n        values: MathCondition.values\n    };\n\n    MathCondition.title = \"Condition\";\n    MathCondition.desc = \"evaluates condition between A and B\";\n\n    MathCondition.prototype.getTitle = function() {\n        return \"A \" + this.properties.OP + \" B\";\n    };\n\n    MathCondition.prototype.onExecute = function() {\n        var A = this.getInputData(0);\n        if (A === undefined) {\n            A = this.properties.A;\n        } else {\n            this.properties.A = A;\n        }\n\n        var B = this.getInputData(1);\n        if (B === undefined) {\n            B = this.properties.B;\n        } else {\n            this.properties.B = B;\n        }\n\n        var result = true;\n        switch (this.properties.OP) {\n            case \">\":\n                result = A > B;\n                break;\n            case \"<\":\n                result = A < B;\n                break;\n            case \"==\":\n                result = A == B;\n                break;\n            case \"!=\":\n                result = A != B;\n                break;\n            case \"<=\":\n                result = A <= B;\n                break;\n            case \">=\":\n                result = A >= B;\n                break;\n            case \"||\":\n                result = A || B;\n                break;\n            case \"&&\":\n                result = A && B;\n                break;\n        }\n\n        this.setOutputData(0, result);\n        this.setOutputData(1, !result);\n    };\n\n    LiteGraph.registerNodeType(\"math/condition\", MathCondition);\n\n\n    function MathBranch() {\n        this.addInput(\"in\", 0);\n        this.addInput(\"cond\", \"boolean\");\n        this.addOutput(\"true\", 0);\n        this.addOutput(\"false\", 0);\n        this.size = [80, 60];\n    }\n\n    MathBranch.title = \"Branch\";\n    MathBranch.desc = \"If condition is true, outputs IN in true, otherwise in false\";\n\n    MathBranch.prototype.onExecute = function() {\n        var V = this.getInputData(0);\n        var cond = this.getInputData(1);\n\n\t\tif(cond)\n\t\t{\n\t\t\tthis.setOutputData(0, V);\n\t\t\tthis.setOutputData(1, null);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis.setOutputData(0, null);\n\t\t\tthis.setOutputData(1, V);\n\t\t}\n\t}\n\n    LiteGraph.registerNodeType(\"math/branch\", MathBranch);\n\n\n    function MathAccumulate() {\n        this.addInput(\"inc\", \"number\");\n        this.addOutput(\"total\", \"number\");\n        this.addProperty(\"increment\", 1);\n        this.addProperty(\"value\", 0);\n    }\n\n    MathAccumulate.title = \"Accumulate\";\n    MathAccumulate.desc = \"Increments a value every time\";\n\n    MathAccumulate.prototype.onExecute = function() {\n        if (this.properties.value === null) {\n            this.properties.value = 0;\n        }\n\n        var inc = this.getInputData(0);\n        if (inc !== null) {\n            this.properties.value += inc;\n        } else {\n            this.properties.value += this.properties.increment;\n        }\n        this.setOutputData(0, this.properties.value);\n    };\n\n    LiteGraph.registerNodeType(\"math/accumulate\", MathAccumulate);\n\n    //Math Trigonometry\n    function MathTrigonometry() {\n        this.addInput(\"v\", \"number\");\n        this.addOutput(\"sin\", \"number\");\n\n        this.addProperty(\"amplitude\", 1);\n        this.addProperty(\"offset\", 0);\n        this.bgImageUrl = \"nodes/imgs/icon-sin.png\";\n    }\n\n    MathTrigonometry.title = \"Trigonometry\";\n    MathTrigonometry.desc = \"Sin Cos Tan\";\n    //MathTrigonometry.filter = \"shader\";\n\n    MathTrigonometry.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            v = 0;\n        }\n        var amplitude = this.properties[\"amplitude\"];\n        var slot = this.findInputSlot(\"amplitude\");\n        if (slot != -1) {\n            amplitude = this.getInputData(slot);\n        }\n        var offset = this.properties[\"offset\"];\n        slot = this.findInputSlot(\"offset\");\n        if (slot != -1) {\n            offset = this.getInputData(slot);\n        }\n\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            var output = this.outputs[i];\n            var value;\n            switch (output.name) {\n                case \"sin\":\n                    value = Math.sin(v);\n                    break;\n                case \"cos\":\n                    value = Math.cos(v);\n                    break;\n                case \"tan\":\n                    value = Math.tan(v);\n                    break;\n                case \"asin\":\n                    value = Math.asin(v);\n                    break;\n                case \"acos\":\n                    value = Math.acos(v);\n                    break;\n                case \"atan\":\n                    value = Math.atan(v);\n                    break;\n            }\n            this.setOutputData(i, amplitude * value + offset);\n        }\n    };\n\n    MathTrigonometry.prototype.onGetInputs = function() {\n        return [[\"v\", \"number\"], [\"amplitude\", \"number\"], [\"offset\", \"number\"]];\n    };\n\n    MathTrigonometry.prototype.onGetOutputs = function() {\n        return [\n            [\"sin\", \"number\"],\n            [\"cos\", \"number\"],\n            [\"tan\", \"number\"],\n            [\"asin\", \"number\"],\n            [\"acos\", \"number\"],\n            [\"atan\", \"number\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/trigonometry\", MathTrigonometry);\n\n    LiteGraph.registerSearchboxExtra(\"math/trigonometry\", \"SIN()\", {\n        outputs: [[\"sin\", \"number\"]],\n        title: \"SIN()\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/trigonometry\", \"COS()\", {\n        outputs: [[\"cos\", \"number\"]],\n        title: \"COS()\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/trigonometry\", \"TAN()\", {\n        outputs: [[\"tan\", \"number\"]],\n        title: \"TAN()\"\n    });\n\n    //math library for safe math operations without eval\n    function MathFormula() {\n        this.addInput(\"x\", \"number\");\n        this.addInput(\"y\", \"number\");\n        this.addOutput(\"\", \"number\");\n        this.properties = { x: 1.0, y: 1.0, formula: \"x+y\" };\n        this.code_widget = this.addWidget(\n            \"text\",\n            \"F(x,y)\",\n            this.properties.formula,\n            function(v, canvas, node) {\n                node.properties.formula = v;\n            }\n        );\n        this.addWidget(\"toggle\", \"allow\", LiteGraph.allow_scripts, function(v) {\n            LiteGraph.allow_scripts = v;\n        });\n        this._func = null;\n    }\n\n    MathFormula.title = \"Formula\";\n    MathFormula.desc = \"Compute formula\";\n    MathFormula.size = [160, 100];\n\n    MathAverageFilter.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"formula\") {\n            this.code_widget.value = value;\n        }\n    };\n\n    MathFormula.prototype.onExecute = function() {\n        if (!LiteGraph.allow_scripts) {\n            return;\n        }\n\n        var x = this.getInputData(0);\n        var y = this.getInputData(1);\n        if (x != null) {\n            this.properties[\"x\"] = x;\n        } else {\n            x = this.properties[\"x\"];\n        }\n\n        if (y != null) {\n            this.properties[\"y\"] = y;\n        } else {\n            y = this.properties[\"y\"];\n        }\n\n        var f = this.properties[\"formula\"];\n\n        var value;\n        try {\n            if (!this._func || this._func_code != this.properties.formula) {\n                this._func = new Function(\n                    \"x\",\n                    \"y\",\n                    \"TIME\",\n                    \"return \" + this.properties.formula\n                );\n                this._func_code = this.properties.formula;\n            }\n            value = this._func(x, y, this.graph.globaltime);\n            this.boxcolor = null;\n        } catch (err) {\n            this.boxcolor = \"red\";\n        }\n        this.setOutputData(0, value);\n    };\n\n    MathFormula.prototype.getTitle = function() {\n        return this._func_code || \"Formula\";\n    };\n\n    MathFormula.prototype.onDrawBackground = function() {\n        var f = this.properties[\"formula\"];\n        if (this.outputs && this.outputs.length) {\n            this.outputs[0].label = f;\n        }\n    };\n\n    LiteGraph.registerNodeType(\"math/formula\", MathFormula);\n\n    function Math3DVec2ToXY() {\n        this.addInput(\"vec2\", \"vec2\");\n        this.addOutput(\"x\", \"number\");\n        this.addOutput(\"y\", \"number\");\n    }\n\n    Math3DVec2ToXY.title = \"Vec2->XY\";\n    Math3DVec2ToXY.desc = \"vector 2 to components\";\n\n    Math3DVec2ToXY.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        this.setOutputData(0, v[0]);\n        this.setOutputData(1, v[1]);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/vec2-to-xy\", Math3DVec2ToXY);\n\n    function Math3DXYToVec2() {\n        this.addInputs([[\"x\", \"number\"], [\"y\", \"number\"]]);\n        this.addOutput(\"vec2\", \"vec2\");\n        this.properties = { x: 0, y: 0 };\n        this._data = new Float32Array(2);\n    }\n\n    Math3DXYToVec2.title = \"XY->Vec2\";\n    Math3DXYToVec2.desc = \"components to vector2\";\n\n    Math3DXYToVec2.prototype.onExecute = function() {\n        var x = this.getInputData(0);\n        if (x == null) {\n            x = this.properties.x;\n        }\n        var y = this.getInputData(1);\n        if (y == null) {\n            y = this.properties.y;\n        }\n\n        var data = this._data;\n        data[0] = x;\n        data[1] = y;\n\n        this.setOutputData(0, data);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/xy-to-vec2\", Math3DXYToVec2);\n\n    function Math3DVec3ToXYZ() {\n        this.addInput(\"vec3\", \"vec3\");\n        this.addOutput(\"x\", \"number\");\n        this.addOutput(\"y\", \"number\");\n        this.addOutput(\"z\", \"number\");\n    }\n\n    Math3DVec3ToXYZ.title = \"Vec3->XYZ\";\n    Math3DVec3ToXYZ.desc = \"vector 3 to components\";\n\n    Math3DVec3ToXYZ.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        this.setOutputData(0, v[0]);\n        this.setOutputData(1, v[1]);\n        this.setOutputData(2, v[2]);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/vec3-to-xyz\", Math3DVec3ToXYZ);\n\n    function Math3DXYZToVec3() {\n        this.addInputs([[\"x\", \"number\"], [\"y\", \"number\"], [\"z\", \"number\"]]);\n        this.addOutput(\"vec3\", \"vec3\");\n        this.properties = { x: 0, y: 0, z: 0 };\n        this._data = new Float32Array(3);\n    }\n\n    Math3DXYZToVec3.title = \"XYZ->Vec3\";\n    Math3DXYZToVec3.desc = \"components to vector3\";\n\n    Math3DXYZToVec3.prototype.onExecute = function() {\n        var x = this.getInputData(0);\n        if (x == null) {\n            x = this.properties.x;\n        }\n        var y = this.getInputData(1);\n        if (y == null) {\n            y = this.properties.y;\n        }\n        var z = this.getInputData(2);\n        if (z == null) {\n            z = this.properties.z;\n        }\n\n        var data = this._data;\n        data[0] = x;\n        data[1] = y;\n        data[2] = z;\n\n        this.setOutputData(0, data);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/xyz-to-vec3\", Math3DXYZToVec3);\n\n    function Math3DVec4ToXYZW() {\n        this.addInput(\"vec4\", \"vec4\");\n        this.addOutput(\"x\", \"number\");\n        this.addOutput(\"y\", \"number\");\n        this.addOutput(\"z\", \"number\");\n        this.addOutput(\"w\", \"number\");\n    }\n\n    Math3DVec4ToXYZW.title = \"Vec4->XYZW\";\n    Math3DVec4ToXYZW.desc = \"vector 4 to components\";\n\n    Math3DVec4ToXYZW.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        this.setOutputData(0, v[0]);\n        this.setOutputData(1, v[1]);\n        this.setOutputData(2, v[2]);\n        this.setOutputData(3, v[3]);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/vec4-to-xyzw\", Math3DVec4ToXYZW);\n\n    function Math3DXYZWToVec4() {\n        this.addInputs([\n            [\"x\", \"number\"],\n            [\"y\", \"number\"],\n            [\"z\", \"number\"],\n            [\"w\", \"number\"]\n        ]);\n        this.addOutput(\"vec4\", \"vec4\");\n        this.properties = { x: 0, y: 0, z: 0, w: 0 };\n        this._data = new Float32Array(4);\n    }\n\n    Math3DXYZWToVec4.title = \"XYZW->Vec4\";\n    Math3DXYZWToVec4.desc = \"components to vector4\";\n\n    Math3DXYZWToVec4.prototype.onExecute = function() {\n        var x = this.getInputData(0);\n        if (x == null) {\n            x = this.properties.x;\n        }\n        var y = this.getInputData(1);\n        if (y == null) {\n            y = this.properties.y;\n        }\n        var z = this.getInputData(2);\n        if (z == null) {\n            z = this.properties.z;\n        }\n        var w = this.getInputData(3);\n        if (w == null) {\n            w = this.properties.w;\n        }\n\n        var data = this._data;\n        data[0] = x;\n        data[1] = y;\n        data[2] = z;\n        data[3] = w;\n\n        this.setOutputData(0, data);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/xyzw-to-vec4\", Math3DXYZWToVec4);\n\n})(this);\n\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n\r\n\tfunction Math3DMat4()\r\n\t{\r\n        this.addInput(\"T\", \"vec3\");\r\n        this.addInput(\"R\", \"vec3\");\r\n        this.addInput(\"S\", \"vec3\");\r\n        this.addOutput(\"mat4\", \"mat4\");\r\n\t\tthis.properties = {\r\n\t\t\t\"T\":[0,0,0],\r\n\t\t\t\"R\":[0,0,0],\r\n\t\t\t\"S\":[1,1,1],\r\n\t\t\tR_in_degrees: true\r\n\t\t};\r\n\t\tthis._result = mat4.create();\r\n\t\tthis._must_update = true;\r\n\t}\r\n\r\n\tMath3DMat4.title = \"mat4\";\r\n\tMath3DMat4.temp_quat = new Float32Array([0,0,0,1]);\r\n\tMath3DMat4.temp_mat4 = new Float32Array(16);\r\n\tMath3DMat4.temp_vec3 = new Float32Array(3);\r\n\r\n\tMath3DMat4.prototype.onPropertyChanged = function(name, value)\r\n\t{\r\n\t\tthis._must_update = true;\r\n\t}\r\n\r\n\tMath3DMat4.prototype.onExecute = function()\r\n\t{\r\n\t\tvar M = this._result;\r\n\t\tvar Q = Math3DMat4.temp_quat;\r\n\t\tvar temp_mat4 = Math3DMat4.temp_mat4;\r\n\t\tvar temp_vec3 = Math3DMat4.temp_vec3;\r\n\r\n\t\tvar T = this.getInputData(0);\r\n\t\tvar R = this.getInputData(1);\r\n\t\tvar S = this.getInputData(2);\r\n\r\n\t\tif( this._must_update || T || R || S )\r\n\t\t{\r\n\t\t\tT = T || this.properties.T;\r\n\t\t\tR = R || this.properties.R;\r\n\t\t\tS = S || this.properties.S;\r\n\t\t\tmat4.identity( M );\r\n\t\t\tmat4.translate( M, M, T );\r\n\t\t\tif(this.properties.R_in_degrees)\r\n\t\t\t{\r\n\t\t\t\ttemp_vec3.set( R );\r\n\t\t\t\tvec3.scale(temp_vec3,temp_vec3,DEG2RAD);\r\n\t\t\t\tquat.fromEuler( Q, temp_vec3 );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tquat.fromEuler( Q, R );\r\n\t\t\tmat4.fromQuat( temp_mat4, Q );\r\n\t\t\tmat4.multiply( M, M, temp_mat4 );\r\n\t\t\tmat4.scale( M, M, S );\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0, M);\t\t\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"math3d/mat4\", Math3DMat4);\r\n\r\n    //Math 3D operation\r\n    function Math3DOperation() {\r\n        this.addInput(\"A\", \"number,vec3\");\r\n        this.addInput(\"B\", \"number,vec3\");\r\n        this.addOutput(\"=\", \"number,vec3\");\r\n        this.addProperty(\"OP\", \"+\", \"enum\", { values: Math3DOperation.values });\r\n\t\tthis._result = vec3.create();\r\n    }\r\n\r\n    Math3DOperation.values = [\"+\", \"-\", \"*\", \"/\", \"%\", \"^\", \"max\", \"min\",\"dot\",\"cross\"];\r\n\r\n    LiteGraph.registerSearchboxExtra(\"math3d/operation\", \"CROSS()\", {\r\n        properties: {\"OP\":\"cross\"},\r\n        title: \"CROSS()\"\r\n    });\r\n\r\n    LiteGraph.registerSearchboxExtra(\"math3d/operation\", \"DOT()\", {\r\n        properties: {\"OP\":\"dot\"},\r\n        title: \"DOT()\"\r\n    });\r\n\r\n\tMath3DOperation.title = \"Operation\";\r\n    Math3DOperation.desc = \"Easy math 3D operators\";\r\n    Math3DOperation[\"@OP\"] = {\r\n        type: \"enum\",\r\n        title: \"operation\",\r\n        values: Math3DOperation.values\r\n    };\r\n    Math3DOperation.size = [100, 60];\r\n\r\n    Math3DOperation.prototype.getTitle = function() {\r\n\t\tif(this.properties.OP == \"max\" || this.properties.OP == \"min\" )\r\n\t\t\treturn this.properties.OP + \"(A,B)\";\r\n        return \"A \" + this.properties.OP + \" B\";\r\n    };\r\n\r\n    Math3DOperation.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        var B = this.getInputData(1);\r\n\t\tif(A == null || B == null)\r\n\t\t\treturn;\r\n\t\tif(A.constructor === Number)\r\n\t\t\tA = [A,A,A];\r\n\t\tif(B.constructor === Number)\r\n\t\t\tB = [B,B,B];\r\n\r\n        var result = this._result;\r\n        switch (this.properties.OP) {\r\n            case \"+\":\r\n                result = vec3.add(result,A,B);\r\n                break;\r\n            case \"-\":\r\n                result = vec3.sub(result,A,B);\r\n                break;\r\n            case \"x\":\r\n            case \"X\":\r\n            case \"*\":\r\n                result = vec3.mul(result,A,B);\r\n                break;\r\n            case \"/\":\r\n                result = vec3.div(result,A,B);\r\n                break;\r\n            case \"%\":\r\n                result[0] = A[0]%B[0];\r\n                result[1] = A[1]%B[1];\r\n                result[2] = A[2]%B[2];\r\n                break;\r\n            case \"^\":\r\n                result[0] = Math.pow(A[0],B[0]);\r\n                result[1] = Math.pow(A[1],B[1]);\r\n                result[2] = Math.pow(A[2],B[2]);\r\n                break;\r\n            case \"max\":\r\n                result[0] = Math.max(A[0],B[0]);\r\n                result[1] = Math.max(A[1],B[1]);\r\n                result[2] = Math.max(A[2],B[2]);\r\n                break;\r\n            case \"min\":\r\n                result[0] = Math.min(A[0],B[0]);\r\n                result[1] = Math.min(A[1],B[1]);\r\n                result[2] = Math.min(A[2],B[2]);\r\n            case \"dot\":\r\n                result = vec3.dot(A,B);\r\n                break;\r\n            case \"cross\":\r\n                vec3.cross(result,A,B);\r\n                break;\r\n            default:\r\n                console.warn(\"Unknown operation: \" + this.properties.OP);\r\n        }\r\n        this.setOutputData(0, result);\r\n    };\r\n\r\n    Math3DOperation.prototype.onDrawBackground = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        ctx.font = \"40px Arial\";\r\n        ctx.fillStyle = \"#666\";\r\n        ctx.textAlign = \"center\";\r\n        ctx.fillText(\r\n            this.properties.OP,\r\n            this.size[0] * 0.5,\r\n            (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5\r\n        );\r\n        ctx.textAlign = \"left\";\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/operation\", Math3DOperation);\r\n\r\n    function Math3DVec3Scale() {\r\n        this.addInput(\"in\", \"vec3\");\r\n        this.addInput(\"f\", \"number\");\r\n        this.addOutput(\"out\", \"vec3\");\r\n        this.properties = { f: 1 };\r\n        this._data = new Float32Array(3);\r\n    }\r\n\r\n    Math3DVec3Scale.title = \"vec3_scale\";\r\n    Math3DVec3Scale.desc = \"scales the components of a vec3\";\r\n\r\n    Math3DVec3Scale.prototype.onExecute = function() {\r\n        var v = this.getInputData(0);\r\n        if (v == null) {\r\n            return;\r\n        }\r\n        var f = this.getInputData(1);\r\n        if (f == null) {\r\n            f = this.properties.f;\r\n        }\r\n\r\n        var data = this._data;\r\n        data[0] = v[0] * f;\r\n        data[1] = v[1] * f;\r\n        data[2] = v[2] * f;\r\n        this.setOutputData(0, data);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/vec3-scale\", Math3DVec3Scale);\r\n\r\n    function Math3DVec3Length() {\r\n        this.addInput(\"in\", \"vec3\");\r\n        this.addOutput(\"out\", \"number\");\r\n    }\r\n\r\n    Math3DVec3Length.title = \"vec3_length\";\r\n    Math3DVec3Length.desc = \"returns the module of a vector\";\r\n\r\n    Math3DVec3Length.prototype.onExecute = function() {\r\n        var v = this.getInputData(0);\r\n        if (v == null) {\r\n            return;\r\n        }\r\n        var dist = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);\r\n        this.setOutputData(0, dist);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/vec3-length\", Math3DVec3Length);\r\n\r\n    function Math3DVec3Normalize() {\r\n        this.addInput(\"in\", \"vec3\");\r\n        this.addOutput(\"out\", \"vec3\");\r\n        this._data = new Float32Array(3);\r\n    }\r\n\r\n    Math3DVec3Normalize.title = \"vec3_normalize\";\r\n    Math3DVec3Normalize.desc = \"returns the vector normalized\";\r\n\r\n    Math3DVec3Normalize.prototype.onExecute = function() {\r\n        var v = this.getInputData(0);\r\n        if (v == null) {\r\n            return;\r\n        }\r\n        var dist = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);\r\n        var data = this._data;\r\n        data[0] = v[0] / dist;\r\n        data[1] = v[1] / dist;\r\n        data[2] = v[2] / dist;\r\n\r\n        this.setOutputData(0, data);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/vec3-normalize\", Math3DVec3Normalize);\r\n\r\n    function Math3DVec3Lerp() {\r\n        this.addInput(\"A\", \"vec3\");\r\n        this.addInput(\"B\", \"vec3\");\r\n        this.addInput(\"f\", \"vec3\");\r\n        this.addOutput(\"out\", \"vec3\");\r\n        this.properties = { f: 0.5 };\r\n        this._data = new Float32Array(3);\r\n    }\r\n\r\n    Math3DVec3Lerp.title = \"vec3_lerp\";\r\n    Math3DVec3Lerp.desc = \"returns the interpolated vector\";\r\n\r\n    Math3DVec3Lerp.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        if (A == null) {\r\n            return;\r\n        }\r\n        var B = this.getInputData(1);\r\n        if (B == null) {\r\n            return;\r\n        }\r\n        var f = this.getInputOrProperty(\"f\");\r\n\r\n        var data = this._data;\r\n        data[0] = A[0] * (1 - f) + B[0] * f;\r\n        data[1] = A[1] * (1 - f) + B[1] * f;\r\n        data[2] = A[2] * (1 - f) + B[2] * f;\r\n\r\n        this.setOutputData(0, data);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/vec3-lerp\", Math3DVec3Lerp);\r\n\r\n    function Math3DVec3Dot() {\r\n        this.addInput(\"A\", \"vec3\");\r\n        this.addInput(\"B\", \"vec3\");\r\n        this.addOutput(\"out\", \"number\");\r\n    }\r\n\r\n    Math3DVec3Dot.title = \"vec3_dot\";\r\n    Math3DVec3Dot.desc = \"returns the dot product\";\r\n\r\n    Math3DVec3Dot.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        if (A == null) {\r\n            return;\r\n        }\r\n        var B = this.getInputData(1);\r\n        if (B == null) {\r\n            return;\r\n        }\r\n\r\n        var dot = A[0] * B[0] + A[1] * B[1] + A[2] * B[2];\r\n        this.setOutputData(0, dot);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/vec3-dot\", Math3DVec3Dot);\r\n\r\n    //if glMatrix is installed...\r\n    if (global.glMatrix) {\r\n        function Math3DQuaternion() {\r\n            this.addOutput(\"quat\", \"quat\");\r\n            this.properties = { x: 0, y: 0, z: 0, w: 1, normalize: false };\r\n            this._value = quat.create();\r\n        }\r\n\r\n        Math3DQuaternion.title = \"Quaternion\";\r\n        Math3DQuaternion.desc = \"quaternion\";\r\n\r\n        Math3DQuaternion.prototype.onExecute = function() {\r\n            this._value[0] = this.getInputOrProperty(\"x\");\r\n            this._value[1] = this.getInputOrProperty(\"y\");\r\n            this._value[2] = this.getInputOrProperty(\"z\");\r\n            this._value[3] = this.getInputOrProperty(\"w\");\r\n            if (this.properties.normalize) {\r\n                quat.normalize(this._value, this._value);\r\n            }\r\n            this.setOutputData(0, this._value);\r\n        };\r\n\r\n        Math3DQuaternion.prototype.onGetInputs = function() {\r\n            return [\r\n                [\"x\", \"number\"],\r\n                [\"y\", \"number\"],\r\n                [\"z\", \"number\"],\r\n                [\"w\", \"number\"]\r\n            ];\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/quaternion\", Math3DQuaternion);\r\n\r\n        function Math3DRotation() {\r\n            this.addInputs([[\"degrees\", \"number\"], [\"axis\", \"vec3\"]]);\r\n            this.addOutput(\"quat\", \"quat\");\r\n            this.properties = { angle: 90.0, axis: vec3.fromValues(0, 1, 0) };\r\n\r\n            this._value = quat.create();\r\n        }\r\n\r\n        Math3DRotation.title = \"Rotation\";\r\n        Math3DRotation.desc = \"quaternion rotation\";\r\n\r\n        Math3DRotation.prototype.onExecute = function() {\r\n            var angle = this.getInputData(0);\r\n            if (angle == null) {\r\n                angle = this.properties.angle;\r\n            }\r\n            var axis = this.getInputData(1);\r\n            if (axis == null) {\r\n                axis = this.properties.axis;\r\n            }\r\n\r\n            var R = quat.setAxisAngle(this._value, axis, angle * 0.0174532925);\r\n            this.setOutputData(0, R);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/rotation\", Math3DRotation);\r\n\r\n\r\n        function MathEulerToQuat() {\r\n            this.addInput(\"euler\", \"vec3\");\r\n            this.addOutput(\"quat\", \"quat\");\r\n            this.properties = { euler:[0,0,0], use_yaw_pitch_roll: false };\r\n\t\t\tthis._degs = vec3.create();\r\n            this._value = quat.create();\r\n        }\r\n\r\n        MathEulerToQuat.title = \"Euler->Quat\";\r\n        MathEulerToQuat.desc = \"Converts euler angles (in degrees) to quaternion\";\r\n\r\n        MathEulerToQuat.prototype.onExecute = function() {\r\n            var euler = this.getInputData(0);\r\n            if (euler == null) {\r\n                euler = this.properties.euler;\r\n            }\r\n\t\t\tvec3.scale( this._degs, euler, DEG2RAD );\r\n\t\t\tif(this.properties.use_yaw_pitch_roll)\r\n\t\t\t\tthis._degs = [this._degs[2],this._degs[0],this._degs[1]];\r\n            var R = quat.fromEuler(this._value, this._degs);\r\n            this.setOutputData(0, R);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/euler_to_quat\", MathEulerToQuat);\r\n\r\n        function MathQuatToEuler() {\r\n            this.addInput([\"quat\", \"quat\"]);\r\n            this.addOutput(\"euler\", \"vec3\");\r\n\t\t\tthis._value = vec3.create();\r\n        }\r\n\r\n        MathQuatToEuler.title = \"Euler->Quat\";\r\n        MathQuatToEuler.desc = \"Converts rotX,rotY,rotZ in degrees to quat\";\r\n\r\n        MathQuatToEuler.prototype.onExecute = function() {\r\n            var q = this.getInputData(0);\r\n\t\t\tif(!q)\r\n\t\t\t\treturn;\r\n            var R = quat.toEuler(this._value, q);\r\n\t\t\tvec3.scale( this._value, this._value, DEG2RAD );\r\n            this.setOutputData(0, this._value);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/quat_to_euler\", MathQuatToEuler);\r\n\r\n\r\n        //Math3D rotate vec3\r\n        function Math3DRotateVec3() {\r\n            this.addInputs([[\"vec3\", \"vec3\"], [\"quat\", \"quat\"]]);\r\n            this.addOutput(\"result\", \"vec3\");\r\n            this.properties = { vec: [0, 0, 1] };\r\n        }\r\n\r\n        Math3DRotateVec3.title = \"Rot. Vec3\";\r\n        Math3DRotateVec3.desc = \"rotate a point\";\r\n\r\n        Math3DRotateVec3.prototype.onExecute = function() {\r\n            var vec = this.getInputData(0);\r\n            if (vec == null) {\r\n                vec = this.properties.vec;\r\n            }\r\n            var quat = this.getInputData(1);\r\n            if (quat == null) {\r\n                this.setOutputData(vec);\r\n            } else {\r\n                this.setOutputData(\r\n                    0,\r\n                    vec3.transformQuat(vec3.create(), vec, quat)\r\n                );\r\n            }\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/rotate_vec3\", Math3DRotateVec3);\r\n\r\n        function Math3DMultQuat() {\r\n            this.addInputs([[\"A\", \"quat\"], [\"B\", \"quat\"]]);\r\n            this.addOutput(\"A*B\", \"quat\");\r\n\r\n            this._value = quat.create();\r\n        }\r\n\r\n        Math3DMultQuat.title = \"Mult. Quat\";\r\n        Math3DMultQuat.desc = \"rotate quaternion\";\r\n\r\n        Math3DMultQuat.prototype.onExecute = function() {\r\n            var A = this.getInputData(0);\r\n            if (A == null) {\r\n                return;\r\n            }\r\n            var B = this.getInputData(1);\r\n            if (B == null) {\r\n                return;\r\n            }\r\n\r\n            var R = quat.multiply(this._value, A, B);\r\n            this.setOutputData(0, R);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/mult-quat\", Math3DMultQuat);\r\n\r\n        function Math3DQuatSlerp() {\r\n            this.addInputs([\r\n                [\"A\", \"quat\"],\r\n                [\"B\", \"quat\"],\r\n                [\"factor\", \"number\"]\r\n            ]);\r\n            this.addOutput(\"slerp\", \"quat\");\r\n            this.addProperty(\"factor\", 0.5);\r\n\r\n            this._value = quat.create();\r\n        }\r\n\r\n        Math3DQuatSlerp.title = \"Quat Slerp\";\r\n        Math3DQuatSlerp.desc = \"quaternion spherical interpolation\";\r\n\r\n        Math3DQuatSlerp.prototype.onExecute = function() {\r\n            var A = this.getInputData(0);\r\n            if (A == null) {\r\n                return;\r\n            }\r\n            var B = this.getInputData(1);\r\n            if (B == null) {\r\n                return;\r\n            }\r\n            var factor = this.properties.factor;\r\n            if (this.getInputData(2) != null) {\r\n                factor = this.getInputData(2);\r\n            }\r\n\r\n            var R = quat.slerp(this._value, A, B, factor);\r\n            this.setOutputData(0, R);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/quat-slerp\", Math3DQuatSlerp);\r\n\r\n\r\n        //Math3D rotate vec3\r\n        function Math3DRemapRange() {\r\n            this.addInput(\"vec3\", \"vec3\");\r\n            this.addOutput(\"remap\", \"vec3\");\r\n\t\t\tthis.addOutput(\"clamped\", \"vec3\");\r\n            this.properties = { clamp: true, range_min: [-1, -1, 0], range_max: [1, 1, 0], target_min: [-1,-1,0], target_max:[1,1,0] };\r\n\t\t\tthis._value = vec3.create();\r\n\t\t\tthis._clamped = vec3.create();\r\n        }\r\n\r\n        Math3DRemapRange.title = \"Remap Range\";\r\n        Math3DRemapRange.desc = \"remap a 3D range\";\r\n\r\n        Math3DRemapRange.prototype.onExecute = function() {\r\n            var vec = this.getInputData(0);\r\n\t\t\tif(vec)\r\n\t\t\t\tthis._value.set(vec);\r\n\t\t\tvar range_min = this.properties.range_min;\r\n\t\t\tvar range_max = this.properties.range_max;\r\n\t\t\tvar target_min = this.properties.target_min;\r\n\t\t\tvar target_max = this.properties.target_max;\r\n\r\n\t\t\t//swap to avoid errors\r\n\t\t\t/*\r\n\t\t\tif(range_min > range_max)\r\n\t\t\t{\r\n\t\t\t\trange_min = range_max;\r\n\t\t\t\trange_max = this.properties.range_min;\r\n\t\t\t}\r\n\r\n\t\t\tif(target_min > target_max)\r\n\t\t\t{\r\n\t\t\t\ttarget_min = target_max;\r\n\t\t\t\ttarget_max = this.properties.target_min;\r\n\t\t\t}\r\n\t\t\t*/\r\n\r\n\t\t\tfor(var i = 0; i < 3; ++i)\r\n\t\t\t{\r\n\t\t\t\tvar r = range_max[i] - range_min[i];\r\n\t\t\t\tthis._clamped[i] = clamp( this._value[i], range_min[i], range_max[i] );\r\n\t\t\t\tif(r == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis._value[i] = (target_min[i] + target_max[i]) * 0.5;\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar n = (this._value[i] - range_min[i]) / r;\r\n\t\t\t\tif(this.properties.clamp)\r\n\t\t\t\t\tn = clamp(n,0,1);\r\n\t\t\t\tvar t = target_max[i] - target_min[i];\r\n\t\t\t\tthis._value[i] = target_min[i] + n * t;\r\n\t\t\t}\r\n\r\n\t\t\tthis.setOutputData(0,this._value);\r\n\t\t\tthis.setOutputData(1,this._clamped);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/remap_range\", Math3DRemapRange);\r\n\r\n\r\n\r\n    } //glMatrix\r\n\telse if (LiteGraph.debug)\r\n\t\tconsole.warn(\"No glmatrix found, some Math3D nodes may not work\");\r\n\r\n})(this);\r\n\n//basic nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    function toString(a) {\r\n\t\tif(a && a.constructor === Object)\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\treturn JSON.stringify(a);\r\n\t\t\t}\r\n\t\t\tcatch (err)\r\n\t\t\t{\r\n\t\t\t\treturn String(a);\r\n\t\t\t}\r\n\t\t}\r\n        return String(a);\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\"string/toString\", toString, [\"\"], \"string\");\r\n\r\n    function compare(a, b) {\r\n        return a == b;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/compare\",\r\n        compare,\r\n        [\"string\", \"string\"],\r\n        \"boolean\"\r\n    );\r\n\r\n    function concatenate(a, b) {\r\n        if (a === undefined) {\r\n            return b;\r\n        }\r\n        if (b === undefined) {\r\n            return a;\r\n        }\r\n        return a + b;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/concatenate\",\r\n        concatenate,\r\n        [\"string\", \"string\"],\r\n        \"string\"\r\n    );\r\n\r\n    function contains(a, b) {\r\n        if (a === undefined || b === undefined) {\r\n            return false;\r\n        }\r\n        return a.indexOf(b) != -1;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/contains\",\r\n        contains,\r\n        [\"string\", \"string\"],\r\n        \"boolean\"\r\n    );\r\n\r\n    function toUpperCase(a) {\r\n        if (a != null && a.constructor === String) {\r\n            return a.toUpperCase();\r\n        }\r\n        return a;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/toUpperCase\",\r\n        toUpperCase,\r\n        [\"string\"],\r\n        \"string\"\r\n    );\r\n\r\n    function split(str, separator) {\r\n\t\tif(separator == null)\r\n\t\t\tseparator = this.properties.separator;\r\n        if (str == null )\r\n\t        return [];\r\n\t\tif( str.constructor === String )\r\n\t\t\treturn str.split(separator || \" \");\r\n\t\telse if( str.constructor === Array )\r\n\t\t{\r\n\t\t\tvar r = [];\r\n\t\t\tfor(var i = 0; i < str.length; ++i){\r\n                if (typeof str[i] == \"string\")\r\n\t\t\t\t    r[i] = str[i].split(separator || \" \");\r\n            }\r\n\t\t\treturn r;\r\n\t\t}\r\n        return null;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/split\",\r\n        split,\r\n        [\"string,array\", \"string\"],\r\n        \"array\",\r\n\t\t{ separator: \",\" }\r\n    );\r\n\r\n    function toFixed(a) {\r\n        if (a != null && a.constructor === Number) {\r\n            return a.toFixed(this.properties.precision);\r\n        }\r\n        return a;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/toFixed\",\r\n        toFixed,\r\n        [\"number\"],\r\n        \"string\",\r\n        { precision: 0 }\r\n    );\r\n\r\n\r\n    function StringToTable() {\r\n        this.addInput(\"\", \"string\");\r\n        this.addOutput(\"table\", \"table\");\r\n        this.addOutput(\"rows\", \"number\");\r\n        this.addProperty(\"value\", \"\");\r\n        this.addProperty(\"separator\", \",\");\r\n\t\tthis._table = null;\r\n    }\r\n\r\n    StringToTable.title = \"toTable\";\r\n    StringToTable.desc = \"Splits a string to table\";\r\n\r\n    StringToTable.prototype.onExecute = function() {\r\n        var input = this.getInputData(0);\r\n\t\tif(!input)\r\n\t\t\treturn;\r\n\t\tvar separator = this.properties.separator || \",\";\r\n\t\tif(input != this._str || separator != this._last_separator )\r\n\t\t{\r\n\t\t\tthis._last_separator = separator;\r\n\t\t\tthis._str = input;\r\n\t\t\tthis._table = input.split(\"\\n\").map(function(a){ return a.trim().split(separator)});\r\n\t\t}\r\n        this.setOutputData(0, this._table );\r\n        this.setOutputData(1, this._table ? this._table.length : 0 );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"string/toTable\", StringToTable);\r\n\r\n})(this);\r\n\n(function(global) {\n    var LiteGraph = global.LiteGraph;\n\n    function Selector() {\n        this.addInput(\"sel\", \"number\");\n        this.addInput(\"A\");\n        this.addInput(\"B\");\n        this.addInput(\"C\");\n        this.addInput(\"D\");\n        this.addOutput(\"out\");\n\n        this.selected = 0;\n    }\n\n    Selector.title = \"Selector\";\n    Selector.desc = \"selects an output\";\n\n    Selector.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n        ctx.fillStyle = \"#AFB\";\n        var y = (this.selected + 1) * LiteGraph.NODE_SLOT_HEIGHT + 6;\n        ctx.beginPath();\n        ctx.moveTo(50, y);\n        ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT);\n        ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5);\n        ctx.fill();\n    };\n\n    Selector.prototype.onExecute = function() {\n        var sel = this.getInputData(0);\n        if (sel == null || sel.constructor !== Number)\n            sel = 0;\n        this.selected = sel = Math.round(sel) % (this.inputs.length - 1);\n        var v = this.getInputData(sel + 1);\n        if (v !== undefined) {\n            this.setOutputData(0, v);\n        }\n    };\n\n    Selector.prototype.onGetInputs = function() {\n        return [[\"E\", 0], [\"F\", 0], [\"G\", 0], [\"H\", 0]];\n    };\n\n    LiteGraph.registerNodeType(\"logic/selector\", Selector);\n\n    function Sequence() {\n        this.properties = {\n            sequence: \"A,B,C\"\n        };\n        this.addInput(\"index\", \"number\");\n        this.addInput(\"seq\");\n        this.addOutput(\"out\");\n\n        this.index = 0;\n        this.values = this.properties.sequence.split(\",\");\n    }\n\n    Sequence.title = \"Sequence\";\n    Sequence.desc = \"select one element from a sequence from a string\";\n\n    Sequence.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"sequence\") {\n            this.values = value.split(\",\");\n        }\n    };\n\n    Sequence.prototype.onExecute = function() {\n        var seq = this.getInputData(1);\n        if (seq && seq != this.current_sequence) {\n            this.values = seq.split(\",\");\n            this.current_sequence = seq;\n        }\n        var index = this.getInputData(0);\n        if (index == null) {\n            index = 0;\n        }\n        this.index = index = Math.round(index) % this.values.length;\n\n        this.setOutputData(0, this.values[index]);\n    };\n\n    LiteGraph.registerNodeType(\"logic/sequence\", Sequence);\n\t\n    \n    function logicAnd(){\n        this.properties = { };\n        this.addInput(\"a\", \"boolean\");\n        this.addInput(\"b\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicAnd.title = \"AND\";\n    logicAnd.desc = \"Return true if all inputs are true\";\n    logicAnd.prototype.onExecute = function() {\n        var ret = true;\n        for (var inX in this.inputs){\n            if (!this.getInputData(inX)){\n                var ret = false;\n                break;\n            }\n        }\n        this.setOutputData(0, ret);\n    };\n    logicAnd.prototype.onGetInputs = function() {\n        return [\n            [\"and\", \"boolean\"]\n        ];\n    };\n    LiteGraph.registerNodeType(\"logic/AND\", logicAnd);\n    \n    \n    function logicOr(){\n        this.properties = { };\n        this.addInput(\"a\", \"boolean\");\n        this.addInput(\"b\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicOr.title = \"OR\";\n    logicOr.desc = \"Return true if at least one input is true\";\n    logicOr.prototype.onExecute = function() {\n        var ret = false;\n        for (var inX in this.inputs){\n            if (this.getInputData(inX)){\n                ret = true;\n                break;\n            }\n        }\n        this.setOutputData(0, ret);\n    };\n    logicOr.prototype.onGetInputs = function() {\n        return [\n            [\"or\", \"boolean\"]\n        ];\n    };\n    LiteGraph.registerNodeType(\"logic/OR\", logicOr);\n    \n    \n    function logicNot(){\n        this.properties = { };\n        this.addInput(\"in\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicNot.title = \"NOT\";\n    logicNot.desc = \"Return the logical negation\";\n    logicNot.prototype.onExecute = function() {\n        var ret = !this.getInputData(0);\n        this.setOutputData(0, ret);\n    };\n    LiteGraph.registerNodeType(\"logic/NOT\", logicNot);\n    \n    \n    function logicCompare(){\n        this.properties = { };\n        this.addInput(\"a\", \"boolean\");\n        this.addInput(\"b\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicCompare.title = \"bool == bool\";\n    logicCompare.desc = \"Compare for logical equality\";\n    logicCompare.prototype.onExecute = function() {\n        var last = null;\n        var ret = true;\n        for (var inX in this.inputs){\n            if (last === null) last = this.getInputData(inX);\n            else\n                if (last != this.getInputData(inX)){\n                    ret = false;\n                    break;\n                }\n        }\n        this.setOutputData(0, ret);\n    };\n    logicCompare.prototype.onGetInputs = function() {\n        return [\n            [\"bool\", \"boolean\"]\n        ];\n    };\n    LiteGraph.registerNodeType(\"logic/CompareBool\", logicCompare);\n    \n    \n    function logicBranch(){\n        this.properties = { };\n        this.addInput(\"onTrigger\", LiteGraph.ACTION);\n        this.addInput(\"condition\", \"boolean\");\n        this.addOutput(\"true\", LiteGraph.EVENT);\n        this.addOutput(\"false\", LiteGraph.EVENT);\n        this.mode = LiteGraph.ON_TRIGGER;\n    }\n    logicBranch.title = \"Branch\";\n    logicBranch.desc = \"Branch execution on condition\";\n    logicBranch.prototype.onExecute = function(param, options) {\n        var condtition = this.getInputData(1);\n        if (condtition){\n            this.triggerSlot(0);\n        }else{\n            this.triggerSlot(1);\n        }\n    };\n    LiteGraph.registerNodeType(\"logic/IF\", logicBranch);\n})(this);\n\n(function(global) {\n    var LiteGraph = global.LiteGraph;\n\n    function GraphicsPlot() {\n        this.addInput(\"A\", \"Number\");\n        this.addInput(\"B\", \"Number\");\n        this.addInput(\"C\", \"Number\");\n        this.addInput(\"D\", \"Number\");\n\n        this.values = [[], [], [], []];\n        this.properties = { scale: 2 };\n    }\n\n    GraphicsPlot.title = \"Plot\";\n    GraphicsPlot.desc = \"Plots data over time\";\n    GraphicsPlot.colors = [\"#FFF\", \"#F99\", \"#9F9\", \"#99F\"];\n\n    GraphicsPlot.prototype.onExecute = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        var size = this.size;\n\n        for (var i = 0; i < 4; ++i) {\n            var v = this.getInputData(i);\n            if (v == null) {\n                continue;\n            }\n            var values = this.values[i];\n            values.push(v);\n            if (values.length > size[0]) {\n                values.shift();\n            }\n        }\n    };\n\n    GraphicsPlot.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        var size = this.size;\n\n        var scale = (0.5 * size[1]) / this.properties.scale;\n        var colors = GraphicsPlot.colors;\n        var offset = size[1] * 0.5;\n\n        ctx.fillStyle = \"#000\";\n        ctx.fillRect(0, 0, size[0], size[1]);\n        ctx.strokeStyle = \"#555\";\n        ctx.beginPath();\n        ctx.moveTo(0, offset);\n        ctx.lineTo(size[0], offset);\n        ctx.stroke();\n\n        if (this.inputs) {\n            for (var i = 0; i < 4; ++i) {\n                var values = this.values[i];\n                if (!this.inputs[i] || !this.inputs[i].link) {\n                    continue;\n                }\n                ctx.strokeStyle = colors[i];\n                ctx.beginPath();\n                var v = values[0] * scale * -1 + offset;\n                ctx.moveTo(0, clamp(v, 0, size[1]));\n                for (var j = 1; j < values.length && j < size[0]; ++j) {\n                    var v = values[j] * scale * -1 + offset;\n                    ctx.lineTo(j, clamp(v, 0, size[1]));\n                }\n                ctx.stroke();\n            }\n        }\n    };\n\n    LiteGraph.registerNodeType(\"graphics/plot\", GraphicsPlot);\n\n    function GraphicsImage() {\n        this.addOutput(\"frame\", \"image\");\n        this.properties = { url: \"\" };\n    }\n\n    GraphicsImage.title = \"Image\";\n    GraphicsImage.desc = \"Image loader\";\n    GraphicsImage.widgets = [{ name: \"load\", text: \"Load\", type: \"button\" }];\n\n    GraphicsImage.supported_extensions = [\"jpg\", \"jpeg\", \"png\", \"gif\"];\n\n    GraphicsImage.prototype.onAdded = function() {\n        if (this.properties[\"url\"] != \"\" && this.img == null) {\n            this.loadImage(this.properties[\"url\"]);\n        }\n    };\n\n    GraphicsImage.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n        if (this.img && this.size[0] > 5 && this.size[1] > 5 && this.img.width) {\n            ctx.drawImage(this.img, 0, 0, this.size[0], this.size[1]);\n        }\n    };\n\n    GraphicsImage.prototype.onExecute = function() {\n        if (!this.img) {\n            this.boxcolor = \"#000\";\n        }\n        if (this.img && this.img.width) {\n            this.setOutputData(0, this.img);\n        } else {\n            this.setOutputData(0, null);\n        }\n        if (this.img && this.img.dirty) {\n            this.img.dirty = false;\n        }\n    };\n\n    GraphicsImage.prototype.onPropertyChanged = function(name, value) {\n        this.properties[name] = value;\n        if (name == \"url\" && value != \"\") {\n            this.loadImage(value);\n        }\n\n        return true;\n    };\n\n    GraphicsImage.prototype.loadImage = function(url, callback) {\n        if (url == \"\") {\n            this.img = null;\n            return;\n        }\n\n        this.img = document.createElement(\"img\");\n\n        if (url.substr(0, 4) == \"http\" && LiteGraph.proxy) {\n            url = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\n        }\n\n        this.img.src = url;\n        this.boxcolor = \"#F95\";\n        var that = this;\n        this.img.onload = function() {\n            if (callback) {\n                callback(this);\n            }\n            console.log( \"Image loaded, size: \" + that.img.width + \"x\" + that.img.height );\n            this.dirty = true;\n            that.boxcolor = \"#9F9\";\n            that.setDirtyCanvas(true);\n        };\n        this.img.onerror = function() {\n\t\t\tconsole.log(\"error loading the image:\" + url);\n\t\t}\n    };\n\n    GraphicsImage.prototype.onWidget = function(e, widget) {\n        if (widget.name == \"load\") {\n            this.loadImage(this.properties[\"url\"]);\n        }\n    };\n\n    GraphicsImage.prototype.onDropFile = function(file) {\n        var that = this;\n        if (this._url) {\n            URL.revokeObjectURL(this._url);\n        }\n        this._url = URL.createObjectURL(file);\n        this.properties.url = this._url;\n        this.loadImage(this._url, function(img) {\n            that.size[1] = (img.height / img.width) * that.size[0];\n        });\n    };\n\n    LiteGraph.registerNodeType(\"graphics/image\", GraphicsImage);\n\n    function ColorPalette() {\n        this.addInput(\"f\", \"number\");\n        this.addOutput(\"Color\", \"color\");\n        this.properties = {\n            colorA: \"#444444\",\n            colorB: \"#44AAFF\",\n            colorC: \"#44FFAA\",\n            colorD: \"#FFFFFF\"\n        };\n    }\n\n    ColorPalette.title = \"Palette\";\n    ColorPalette.desc = \"Generates a color\";\n\n    ColorPalette.prototype.onExecute = function() {\n        var c = [];\n\n        if (this.properties.colorA != null) {\n            c.push(hex2num(this.properties.colorA));\n        }\n        if (this.properties.colorB != null) {\n            c.push(hex2num(this.properties.colorB));\n        }\n        if (this.properties.colorC != null) {\n            c.push(hex2num(this.properties.colorC));\n        }\n        if (this.properties.colorD != null) {\n            c.push(hex2num(this.properties.colorD));\n        }\n\n        var f = this.getInputData(0);\n        if (f == null) {\n            f = 0.5;\n        }\n        if (f > 1.0) {\n            f = 1.0;\n        } else if (f < 0.0) {\n            f = 0.0;\n        }\n\n        if (c.length == 0) {\n            return;\n        }\n\n        var result = [0, 0, 0];\n        if (f == 0) {\n            result = c[0];\n        } else if (f == 1) {\n            result = c[c.length - 1];\n        } else {\n            var pos = (c.length - 1) * f;\n            var c1 = c[Math.floor(pos)];\n            var c2 = c[Math.floor(pos) + 1];\n            var t = pos - Math.floor(pos);\n            result[0] = c1[0] * (1 - t) + c2[0] * t;\n            result[1] = c1[1] * (1 - t) + c2[1] * t;\n            result[2] = c1[2] * (1 - t) + c2[2] * t;\n        }\n\n        /*\n\tc[0] = 1.0 - Math.abs( Math.sin( 0.1 * reModular.getTime() * Math.PI) );\n\tc[1] = Math.abs( Math.sin( 0.07 * reModular.getTime() * Math.PI) );\n\tc[2] = Math.abs( Math.sin( 0.01 * reModular.getTime() * Math.PI) );\n\t*/\n\n        for (var i=0; i < result.length; i++) {\n            result[i] /= 255;\n        }\n\n        this.boxcolor = colorToString(result);\n        this.setOutputData(0, result);\n    };\n\n    LiteGraph.registerNodeType(\"color/palette\", ColorPalette);\n\n    function ImageFrame() {\n        this.addInput(\"\", \"image,canvas\");\n        this.size = [200, 200];\n    }\n\n    ImageFrame.title = \"Frame\";\n    ImageFrame.desc = \"Frame viewerew\";\n    ImageFrame.widgets = [\n        { name: \"resize\", text: \"Resize box\", type: \"button\" },\n        { name: \"view\", text: \"View Image\", type: \"button\" }\n    ];\n\n    ImageFrame.prototype.onDrawBackground = function(ctx) {\n        if (this.frame && !this.flags.collapsed) {\n            ctx.drawImage(this.frame, 0, 0, this.size[0], this.size[1]);\n        }\n    };\n\n    ImageFrame.prototype.onExecute = function() {\n        this.frame = this.getInputData(0);\n        this.setDirtyCanvas(true);\n    };\n\n    ImageFrame.prototype.onWidget = function(e, widget) {\n        if (widget.name == \"resize\" && this.frame) {\n            var width = this.frame.width;\n            var height = this.frame.height;\n\n            if (!width && this.frame.videoWidth != null) {\n                width = this.frame.videoWidth;\n                height = this.frame.videoHeight;\n            }\n\n            if (width && height) {\n                this.size = [width, height];\n            }\n            this.setDirtyCanvas(true, true);\n        } else if (widget.name == \"view\") {\n            this.show();\n        }\n    };\n\n    ImageFrame.prototype.show = function() {\n        //var str = this.canvas.toDataURL(\"image/png\");\n        if (showElement && this.frame) {\n            showElement(this.frame);\n        }\n    };\n\n    LiteGraph.registerNodeType(\"graphics/frame\", ImageFrame);\n\n    function ImageFade() {\n        this.addInputs([\n            [\"img1\", \"image\"],\n            [\"img2\", \"image\"],\n            [\"fade\", \"number\"]\n        ]);\n        this.addOutput(\"\", \"image\");\n        this.properties = { fade: 0.5, width: 512, height: 512 };\n    }\n\n    ImageFade.title = \"Image fade\";\n    ImageFade.desc = \"Fades between images\";\n    ImageFade.widgets = [\n        { name: \"resizeA\", text: \"Resize to A\", type: \"button\" },\n        { name: \"resizeB\", text: \"Resize to B\", type: \"button\" }\n    ];\n\n    ImageFade.prototype.onAdded = function() {\n        this.createCanvas();\n        var ctx = this.canvas.getContext(\"2d\");\n        ctx.fillStyle = \"#000\";\n        ctx.fillRect(0, 0, this.properties[\"width\"], this.properties[\"height\"]);\n    };\n\n    ImageFade.prototype.createCanvas = function() {\n        this.canvas = document.createElement(\"canvas\");\n        this.canvas.width = this.properties[\"width\"];\n        this.canvas.height = this.properties[\"height\"];\n    };\n\n    ImageFade.prototype.onExecute = function() {\n        var ctx = this.canvas.getContext(\"2d\");\n        this.canvas.width = this.canvas.width;\n\n        var A = this.getInputData(0);\n        if (A != null) {\n            ctx.drawImage(A, 0, 0, this.canvas.width, this.canvas.height);\n        }\n\n        var fade = this.getInputData(2);\n        if (fade == null) {\n            fade = this.properties[\"fade\"];\n        } else {\n            this.properties[\"fade\"] = fade;\n        }\n\n        ctx.globalAlpha = fade;\n        var B = this.getInputData(1);\n        if (B != null) {\n            ctx.drawImage(B, 0, 0, this.canvas.width, this.canvas.height);\n        }\n        ctx.globalAlpha = 1.0;\n\n        this.setOutputData(0, this.canvas);\n        this.setDirtyCanvas(true);\n    };\n\n    LiteGraph.registerNodeType(\"graphics/imagefade\", ImageFade);\n\n    function ImageCrop() {\n        this.addInput(\"\", \"image\");\n        this.addOutput(\"\", \"image\");\n        this.properties = { width: 256, height: 256, x: 0, y: 0, scale: 1.0 };\n        this.size = [50, 20];\n    }\n\n    ImageCrop.title = \"Crop\";\n    ImageCrop.desc = \"Crop Image\";\n\n    ImageCrop.prototype.onAdded = function() {\n        this.createCanvas();\n    };\n\n    ImageCrop.prototype.createCanvas = function() {\n        this.canvas = document.createElement(\"canvas\");\n        this.canvas.width = this.properties[\"width\"];\n        this.canvas.height = this.properties[\"height\"];\n    };\n\n    ImageCrop.prototype.onExecute = function() {\n        var input = this.getInputData(0);\n        if (!input) {\n            return;\n        }\n\n        if (input.width) {\n            var ctx = this.canvas.getContext(\"2d\");\n\n            ctx.drawImage(\n                input,\n                -this.properties[\"x\"],\n                -this.properties[\"y\"],\n                input.width * this.properties[\"scale\"],\n                input.height * this.properties[\"scale\"]\n            );\n            this.setOutputData(0, this.canvas);\n        } else {\n            this.setOutputData(0, null);\n        }\n    };\n\n    ImageCrop.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n        if (this.canvas) {\n            ctx.drawImage(\n                this.canvas,\n                0,\n                0,\n                this.canvas.width,\n                this.canvas.height,\n                0,\n                0,\n                this.size[0],\n                this.size[1]\n            );\n        }\n    };\n\n    ImageCrop.prototype.onPropertyChanged = function(name, value) {\n        this.properties[name] = value;\n\n        if (name == \"scale\") {\n            this.properties[name] = parseFloat(value);\n            if (this.properties[name] == 0) {\n                console.error(\"Error in scale\");\n                this.properties[name] = 1.0;\n            }\n        } else {\n            this.properties[name] = parseInt(value);\n        }\n\n        this.createCanvas();\n\n        return true;\n    };\n\n    LiteGraph.registerNodeType(\"graphics/cropImage\", ImageCrop);\n\n    //CANVAS stuff\n\n    function CanvasNode() {\n        this.addInput(\"clear\", LiteGraph.ACTION);\n        this.addOutput(\"\", \"canvas\");\n        this.properties = { width: 512, height: 512, autoclear: true };\n\n        this.canvas = document.createElement(\"canvas\");\n        this.ctx = this.canvas.getContext(\"2d\");\n    }\n\n    CanvasNode.title = \"Canvas\";\n    CanvasNode.desc = \"Canvas to render stuff\";\n\n    CanvasNode.prototype.onExecute = function() {\n        var canvas = this.canvas;\n        var w = this.properties.width | 0;\n        var h = this.properties.height | 0;\n        if (canvas.width != w) {\n            canvas.width = w;\n        }\n        if (canvas.height != h) {\n            canvas.height = h;\n        }\n\n        if (this.properties.autoclear) {\n            this.ctx.clearRect(0, 0, canvas.width, canvas.height);\n        }\n        this.setOutputData(0, canvas);\n    };\n\n    CanvasNode.prototype.onAction = function(action, param) {\n        if (action == \"clear\") {\n            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n        }\n    };\n\n    LiteGraph.registerNodeType(\"graphics/canvas\", CanvasNode);\n\n    function DrawImageNode() {\n        this.addInput(\"canvas\", \"canvas\");\n        this.addInput(\"img\", \"image,canvas\");\n        this.addInput(\"x\", \"number\");\n        this.addInput(\"y\", \"number\");\n        this.properties = { x: 0, y: 0, opacity: 1 };\n    }\n\n    DrawImageNode.title = \"DrawImage\";\n    DrawImageNode.desc = \"Draws image into a canvas\";\n\n    DrawImageNode.prototype.onExecute = function() {\n        var canvas = this.getInputData(0);\n        if (!canvas) {\n            return;\n        }\n\n        var img = this.getInputOrProperty(\"img\");\n        if (!img) {\n            return;\n        }\n\n        var x = this.getInputOrProperty(\"x\");\n        var y = this.getInputOrProperty(\"y\");\n        var ctx = canvas.getContext(\"2d\");\n        ctx.drawImage(img, x, y);\n    };\n\n    LiteGraph.registerNodeType(\"graphics/drawImage\", DrawImageNode);\n\n    function DrawRectangleNode() {\n        this.addInput(\"canvas\", \"canvas\");\n        this.addInput(\"x\", \"number\");\n        this.addInput(\"y\", \"number\");\n        this.addInput(\"w\", \"number\");\n        this.addInput(\"h\", \"number\");\n        this.properties = {\n            x: 0,\n            y: 0,\n            w: 10,\n            h: 10,\n            color: \"white\",\n            opacity: 1\n        };\n    }\n\n    DrawRectangleNode.title = \"DrawRectangle\";\n    DrawRectangleNode.desc = \"Draws rectangle in canvas\";\n\n    DrawRectangleNode.prototype.onExecute = function() {\n        var canvas = this.getInputData(0);\n        if (!canvas) {\n            return;\n        }\n\n        var x = this.getInputOrProperty(\"x\");\n        var y = this.getInputOrProperty(\"y\");\n        var w = this.getInputOrProperty(\"w\");\n        var h = this.getInputOrProperty(\"h\");\n        var ctx = canvas.getContext(\"2d\");\n        ctx.fillRect(x, y, w, h);\n    };\n\n    LiteGraph.registerNodeType(\"graphics/drawRectangle\", DrawRectangleNode);\n\n    function ImageVideo() {\n        this.addInput(\"t\", \"number\");\n        this.addOutputs([[\"frame\", \"image\"], [\"t\", \"number\"], [\"d\", \"number\"]]);\n        this.properties = { url: \"\", use_proxy: true };\n    }\n\n    ImageVideo.title = \"Video\";\n    ImageVideo.desc = \"Video playback\";\n    ImageVideo.widgets = [\n        { name: \"play\", text: \"PLAY\", type: \"minibutton\" },\n        { name: \"stop\", text: \"STOP\", type: \"minibutton\" },\n        { name: \"demo\", text: \"Demo video\", type: \"button\" },\n        { name: \"mute\", text: \"Mute video\", type: \"button\" }\n    ];\n\n    ImageVideo.prototype.onExecute = function() {\n        if (!this.properties.url) {\n            return;\n        }\n\n        if (this.properties.url != this._video_url) {\n            this.loadVideo(this.properties.url);\n        }\n\n        if (!this._video || this._video.width == 0) {\n            return;\n        }\n\n        var t = this.getInputData(0);\n        if (t && t >= 0 && t <= 1.0) {\n            this._video.currentTime = t * this._video.duration;\n            this._video.pause();\n        }\n\n        this._video.dirty = true;\n        this.setOutputData(0, this._video);\n        this.setOutputData(1, this._video.currentTime);\n        this.setOutputData(2, this._video.duration);\n        this.setDirtyCanvas(true);\n    };\n\n    ImageVideo.prototype.onStart = function() {\n        this.play();\n    };\n\n    ImageVideo.prototype.onStop = function() {\n        this.stop();\n    };\n\n    ImageVideo.prototype.loadVideo = function(url) {\n        this._video_url = url;\n\n\t\tvar pos = url.substr(0,10).indexOf(\":\");\n\t\tvar protocol = \"\";\n\t\tif(pos != -1)\n\t\t\tprotocol = url.substr(0,pos);\n\n\t\tvar host = \"\";\n\t\tif(protocol)\n\t\t{\n\t\t\thost = url.substr(0,url.indexOf(\"/\",protocol.length + 3));\n\t\t\thost = host.substr(protocol.length+3);\n\t\t}\n\n        if (\n            this.properties.use_proxy &&\n            protocol &&\n            LiteGraph.proxy &&\n\t\t\thost != location.host\n        ) {\n            url = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\n        }\n\n        this._video = document.createElement(\"video\");\n        this._video.src = url;\n        this._video.type = \"type=video/mp4\";\n\n        this._video.muted = true;\n        this._video.autoplay = true;\n\n        var that = this;\n        this._video.addEventListener(\"loadedmetadata\", function(e) {\n            //onload\n            console.log(\"Duration: \" + this.duration + \" seconds\");\n            console.log(\"Size: \" + this.videoWidth + \",\" + this.videoHeight);\n            that.setDirtyCanvas(true);\n            this.width = this.videoWidth;\n            this.height = this.videoHeight;\n        });\n        this._video.addEventListener(\"progress\", function(e) {\n            //onload\n            console.log(\"video loading...\");\n        });\n        this._video.addEventListener(\"error\", function(e) {\n            console.error(\"Error loading video: \" + this.src);\n            if (this.error) {\n                switch (this.error.code) {\n                    case this.error.MEDIA_ERR_ABORTED:\n                        console.error(\"You stopped the video.\");\n                        break;\n                    case this.error.MEDIA_ERR_NETWORK:\n                        console.error(\"Network error - please try again later.\");\n                        break;\n                    case this.error.MEDIA_ERR_DECODE:\n                        console.error(\"Video is broken..\");\n                        break;\n                    case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:\n                        console.error(\"Sorry, your browser can't play this video.\");\n                        break;\n                }\n            }\n        });\n\n        this._video.addEventListener(\"ended\", function(e) {\n            console.log(\"Video Ended.\");\n            this.play(); //loop\n        });\n\n        //document.body.appendChild(this.video);\n    };\n\n    ImageVideo.prototype.onPropertyChanged = function(name, value) {\n        this.properties[name] = value;\n        if (name == \"url\" && value != \"\") {\n            this.loadVideo(value);\n        }\n\n        return true;\n    };\n\n    ImageVideo.prototype.play = function() {\n        if (this._video && this._video.videoWidth ) { //is loaded\n            this._video.play();\n        }\n    };\n\n    ImageVideo.prototype.playPause = function() {\n        if (!this._video) {\n            return;\n        }\n        if (this._video.paused) {\n            this.play();\n        } else {\n            this.pause();\n        }\n    };\n\n    ImageVideo.prototype.stop = function() {\n        if (!this._video) {\n            return;\n        }\n        this._video.pause();\n        this._video.currentTime = 0;\n    };\n\n    ImageVideo.prototype.pause = function() {\n        if (!this._video) {\n            return;\n        }\n        console.log(\"Video paused\");\n        this._video.pause();\n    };\n\n    ImageVideo.prototype.onWidget = function(e, widget) {\n        /*\n\tif(widget.name == \"demo\")\n\t{\n\t\tthis.loadVideo();\n\t}\n\telse if(widget.name == \"play\")\n\t{\n\t\tif(this._video)\n\t\t\tthis.playPause();\n\t}\n\tif(widget.name == \"stop\")\n\t{\n\t\tthis.stop();\n\t}\n\telse if(widget.name == \"mute\")\n\t{\n\t\tif(this._video)\n\t\t\tthis._video.muted = !this._video.muted;\n\t}\n\t*/\n    };\n\n    LiteGraph.registerNodeType(\"graphics/video\", ImageVideo);\n\n    // Texture Webcam *****************************************\n    function ImageWebcam() {\n        this.addOutput(\"Webcam\", \"image\");\n        this.properties = { filterFacingMode: false, facingMode: \"user\" };\n        this.boxcolor = \"black\";\n        this.frame = 0;\n    }\n\n    ImageWebcam.title = \"Webcam\";\n    ImageWebcam.desc = \"Webcam image\";\n    ImageWebcam.is_webcam_open = false;\n\n    ImageWebcam.prototype.openStream = function() {\n        if (!navigator.mediaDevices.getUserMedia) {\n            console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags');\n            return;\n        }\n\n        this._waiting_confirmation = true;\n\n        // Not showing vendor prefixes.\n        var constraints = {\n            audio: false,\n            video: !this.properties.filterFacingMode ? true : { facingMode: this.properties.facingMode }\n        };\n        navigator.mediaDevices\n            .getUserMedia(constraints)\n            .then(this.streamReady.bind(this))\n            .catch(onFailSoHard);\n\n        var that = this;\n        function onFailSoHard(e) {\n            console.log(\"Webcam rejected\", e);\n            that._webcam_stream = false;\n            ImageWebcam.is_webcam_open = false;\n            that.boxcolor = \"red\";\n            that.trigger(\"stream_error\");\n        }\n    };\n\n    ImageWebcam.prototype.closeStream = function() {\n        if (this._webcam_stream) {\n            var tracks = this._webcam_stream.getTracks();\n            if (tracks.length) {\n                for (var i = 0; i < tracks.length; ++i) {\n                    tracks[i].stop();\n                }\n            }\n            ImageWebcam.is_webcam_open = false;\n            this._webcam_stream = null;\n            this._video = null;\n            this.boxcolor = \"black\";\n            this.trigger(\"stream_closed\");\n        }\n    };\n\n    ImageWebcam.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"facingMode\") {\n            this.properties.facingMode = value;\n            this.closeStream();\n            this.openStream();\n        }\n    };\n\n    ImageWebcam.prototype.onRemoved = function() {\n        this.closeStream();\n    };\n\n    ImageWebcam.prototype.streamReady = function(localMediaStream) {\n        this._webcam_stream = localMediaStream;\n        //this._waiting_confirmation = false;\n        this.boxcolor = \"green\";\n\n        var video = this._video;\n        if (!video) {\n            video = document.createElement(\"video\");\n            video.autoplay = true;\n            video.srcObject = localMediaStream;\n            this._video = video;\n            //document.body.appendChild( video ); //debug\n            //when video info is loaded (size and so)\n            video.onloadedmetadata = function(e) {\n                // Ready to go. Do some stuff.\n                console.log(e);\n                ImageWebcam.is_webcam_open = true;\n            };\n        }\n\n        this.trigger(\"stream_ready\", video);\n    };\n\n    ImageWebcam.prototype.onExecute = function() {\n        if (this._webcam_stream == null && !this._waiting_confirmation) {\n            this.openStream();\n        }\n\n        if (!this._video || !this._video.videoWidth) {\n            return;\n        }\n\n        this._video.frame = ++this.frame;\n        this._video.width = this._video.videoWidth;\n        this._video.height = this._video.videoHeight;\n        this.setOutputData(0, this._video);\n        for (var i = 1; i < this.outputs.length; ++i) {\n            if (!this.outputs[i]) {\n                continue;\n            }\n            switch (this.outputs[i].name) {\n                case \"width\":\n                    this.setOutputData(i, this._video.videoWidth);\n                    break;\n                case \"height\":\n                    this.setOutputData(i, this._video.videoHeight);\n                    break;\n            }\n        }\n    };\n\n    ImageWebcam.prototype.getExtraMenuOptions = function(graphcanvas) {\n        var that = this;\n        var txt = !that.properties.show ? \"Show Frame\" : \"Hide Frame\";\n        return [\n            {\n                content: txt,\n                callback: function() {\n                    that.properties.show = !that.properties.show;\n                }\n            }\n        ];\n    };\n\n    ImageWebcam.prototype.onDrawBackground = function(ctx) {\n        if (\n            this.flags.collapsed ||\n            this.size[1] <= 20 ||\n            !this.properties.show\n        ) {\n            return;\n        }\n\n        if (!this._video) {\n            return;\n        }\n\n        //render to graph canvas\n        ctx.save();\n        ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]);\n        ctx.restore();\n    };\n\n    ImageWebcam.prototype.onGetOutputs = function() {\n        return [\n            [\"width\", \"number\"],\n            [\"height\", \"number\"],\n            [\"stream_ready\", LiteGraph.EVENT],\n            [\"stream_closed\", LiteGraph.EVENT],\n            [\"stream_error\", LiteGraph.EVENT]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"graphics/webcam\", ImageWebcam);\n})(this);\n\n(function(global) {\n    var LiteGraph = global.LiteGraph;\n\tvar LGraphCanvas = global.LGraphCanvas;\n\n    //Works with Litegl.js to create WebGL nodes\n    global.LGraphTexture = null;\n\n    if (typeof GL == \"undefined\")\n\t\treturn;\n\n\tLGraphCanvas.link_type_colors[\"Texture\"] = \"#987\";\n\n\tfunction LGraphTexture() {\n\t\tthis.addOutput(\"tex\", \"Texture\");\n\t\tthis.addOutput(\"name\", \"string\");\n\t\tthis.properties = { name: \"\", filter: true };\n\t\tthis.size = [\n\t\t\tLGraphTexture.image_preview_size,\n\t\t\tLGraphTexture.image_preview_size\n\t\t];\n\t}\n\n\tglobal.LGraphTexture = LGraphTexture;\n\n\tLGraphTexture.title = \"Texture\";\n\tLGraphTexture.desc = \"Texture\";\n\tLGraphTexture.widgets_info = {\n\t\tname: { widget: \"texture\" },\n\t\tfilter: { widget: \"checkbox\" }\n\t};\n\n\t//REPLACE THIS TO INTEGRATE WITH YOUR FRAMEWORK\n\tLGraphTexture.loadTextureCallback = null; //function in charge of loading textures when not present in the container\n\tLGraphTexture.image_preview_size = 256;\n\n\t//flags to choose output texture type\n\tLGraphTexture.UNDEFINED = 0; //not specified\n\tLGraphTexture.PASS_THROUGH = 1; //do not apply FX (like disable but passing the in to the out)\n\tLGraphTexture.COPY = 2; //create new texture with the same properties as the origin texture\n\tLGraphTexture.LOW = 3; //create new texture with low precision (byte)\n\tLGraphTexture.HIGH = 4; //create new texture with high precision (half-float)\n\tLGraphTexture.REUSE = 5; //reuse input texture\n\tLGraphTexture.DEFAULT = 2; //use the default\n\n\tLGraphTexture.MODE_VALUES = {\n\t\t\"undefined\": LGraphTexture.UNDEFINED,\n\t\t\"pass through\": LGraphTexture.PASS_THROUGH,\n\t\tcopy: LGraphTexture.COPY,\n\t\tlow: LGraphTexture.LOW,\n\t\thigh: LGraphTexture.HIGH,\n\t\treuse: LGraphTexture.REUSE,\n\t\tdefault: LGraphTexture.DEFAULT\n\t};\n\n\t//returns the container where all the loaded textures are stored (overwrite if you have a Resources Manager)\n\tLGraphTexture.getTexturesContainer = function() {\n\t\treturn gl.textures;\n\t};\n\n\t//process the loading of a texture (overwrite it if you have a Resources Manager)\n\tLGraphTexture.loadTexture = function(name, options) {\n\t\toptions = options || {};\n\t\tvar url = name;\n\t\tif (url.substr(0, 7) == \"http://\") {\n\t\t\tif (LiteGraph.proxy) {\n\t\t\t\t//proxy external files\n\t\t\t\turl = LiteGraph.proxy + url.substr(7);\n\t\t\t}\n\t\t}\n\n\t\tvar container = LGraphTexture.getTexturesContainer();\n\t\tvar tex = (container[name] = GL.Texture.fromURL(url, options));\n\t\treturn tex;\n\t};\n\n\tLGraphTexture.getTexture = function(name) {\n\t\tvar container = this.getTexturesContainer();\n\n\t\tif (!container) {\n\t\t\tthrow \"Cannot load texture, container of textures not found\";\n\t\t}\n\n\t\tvar tex = container[name];\n\t\tif (!tex && name && name[0] != \":\") {\n\t\t\treturn this.loadTexture(name);\n\t\t}\n\n\t\treturn tex;\n\t};\n\n\t//used to compute the appropiate output texture\n\tLGraphTexture.getTargetTexture = function(origin, target, mode) {\n\t\tif (!origin) {\n\t\t\tthrow \"LGraphTexture.getTargetTexture expects a reference texture\";\n\t\t}\n\n\t\tvar tex_type = null;\n\n\t\tswitch (mode) {\n\t\t\tcase LGraphTexture.LOW:\n\t\t\t\ttex_type = gl.UNSIGNED_BYTE;\n\t\t\t\tbreak;\n\t\t\tcase LGraphTexture.HIGH:\n\t\t\t\ttex_type = gl.HIGH_PRECISION_FORMAT;\n\t\t\t\tbreak;\n\t\t\tcase LGraphTexture.REUSE:\n\t\t\t\treturn origin;\n\t\t\t\tbreak;\n\t\t\tcase LGraphTexture.COPY:\n\t\t\tdefault:\n\t\t\t\ttex_type = origin ? origin.type : gl.UNSIGNED_BYTE;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (\n\t\t\t!target ||\n\t\t\ttarget.width != origin.width ||\n\t\t\ttarget.height != origin.height ||\n\t\t\ttarget.type != tex_type ||\n\t\t\ttarget.format != origin.format \n\t\t) {\n\t\t\ttarget = new GL.Texture(origin.width, origin.height, {\n\t\t\t\ttype: tex_type,\n\t\t\t\tformat: origin.format,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\treturn target;\n\t};\n\n\tLGraphTexture.getTextureType = function(precision, ref_texture) {\n\t\tvar type = ref_texture ? ref_texture.type : gl.UNSIGNED_BYTE;\n\t\tswitch (precision) {\n\t\t\tcase LGraphTexture.HIGH:\n\t\t\t\ttype = gl.HIGH_PRECISION_FORMAT;\n\t\t\t\tbreak;\n\t\t\tcase LGraphTexture.LOW:\n\t\t\t\ttype = gl.UNSIGNED_BYTE;\n\t\t\t\tbreak;\n\t\t\t//no default\n\t\t}\n\t\treturn type;\n\t};\n\n\tLGraphTexture.getWhiteTexture = function() {\n\t\tif (this._white_texture) {\n\t\t\treturn this._white_texture;\n\t\t}\n\t\tvar texture = (this._white_texture = GL.Texture.fromMemory(\n\t\t\t1,\n\t\t\t1,\n\t\t\t[255, 255, 255, 255],\n\t\t\t{ format: gl.RGBA, wrap: gl.REPEAT, filter: gl.NEAREST }\n\t\t));\n\t\treturn texture;\n\t};\n\n\tLGraphTexture.getNoiseTexture = function() {\n\t\tif (this._noise_texture) {\n\t\t\treturn this._noise_texture;\n\t\t}\n\n\t\tvar noise = new Uint8Array(512 * 512 * 4);\n\t\tfor (var i = 0; i < 512 * 512 * 4; ++i) {\n\t\t\tnoise[i] = Math.random() * 255;\n\t\t}\n\n\t\tvar texture = GL.Texture.fromMemory(512, 512, noise, {\n\t\t\tformat: gl.RGBA,\n\t\t\twrap: gl.REPEAT,\n\t\t\tfilter: gl.NEAREST\n\t\t});\n\t\tthis._noise_texture = texture;\n\t\treturn texture;\n\t};\n\n\tLGraphTexture.prototype.onDropFile = function(data, filename, file) {\n\t\tif (!data) {\n\t\t\tthis._drop_texture = null;\n\t\t\tthis.properties.name = \"\";\n\t\t} else {\n\t\t\tvar texture = null;\n\t\t\tif (typeof data == \"string\") {\n\t\t\t\ttexture = GL.Texture.fromURL(data);\n\t\t\t} else if (filename.toLowerCase().indexOf(\".dds\") != -1) {\n\t\t\t\ttexture = GL.Texture.fromDDSInMemory(data);\n\t\t\t} else {\n\t\t\t\tvar blob = new Blob([file]);\n\t\t\t\tvar url = URL.createObjectURL(blob);\n\t\t\t\ttexture = GL.Texture.fromURL(url);\n\t\t\t}\n\n\t\t\tthis._drop_texture = texture;\n\t\t\tthis.properties.name = filename;\n\t\t}\n\t};\n\n\tLGraphTexture.prototype.getExtraMenuOptions = function(graphcanvas) {\n\t\tvar that = this;\n\t\tif (!this._drop_texture) {\n\t\t\treturn;\n\t\t}\n\t\treturn [\n\t\t\t{\n\t\t\t\tcontent: \"Clear\",\n\t\t\t\tcallback: function() {\n\t\t\t\t\tthat._drop_texture = null;\n\t\t\t\t\tthat.properties.name = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t];\n\t};\n\n\tLGraphTexture.prototype.onExecute = function() {\n\t\tvar tex = null;\n\t\tif (this.isOutputConnected(1)) {\n\t\t\ttex = this.getInputData(0);\n\t\t}\n\n\t\tif (!tex && this._drop_texture) {\n\t\t\ttex = this._drop_texture;\n\t\t}\n\n\t\tif (!tex && this.properties.name) {\n\t\t\ttex = LGraphTexture.getTexture(this.properties.name);\n\t\t}\n\n\t\tif (!tex) {\n\t\t\tthis.setOutputData( 0, null );\n\t\t\tthis.setOutputData( 1, \"\" );\n\t\t\treturn;\n\t\t}\n\n\t\tthis._last_tex = tex;\n\n\t\tif (this.properties.filter === false) {\n\t\t\ttex.setParameter(gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n\t\t} else {\n\t\t\ttex.setParameter(gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n\t\t}\n\n\t\tthis.setOutputData( 0, tex );\n\t\tthis.setOutputData( 1, tex.fullpath || tex.filename );\n\n\t\tfor (var i = 2; i < this.outputs.length; i++) {\n\t\t\tvar output = this.outputs[i];\n\t\t\tif (!output) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvar v = null;\n\t\t\tif (output.name == \"width\") {\n\t\t\t\tv = tex.width;\n\t\t\t} else if (output.name == \"height\") {\n\t\t\t\tv = tex.height;\n\t\t\t} else if (output.name == \"aspect\") {\n\t\t\t\tv = tex.width / tex.height;\n\t\t\t}\n\t\t\tthis.setOutputData(i, v);\n\t\t}\n\t};\n\n\tLGraphTexture.prototype.onResourceRenamed = function(\n\t\told_name,\n\t\tnew_name\n\t) {\n\t\tif (this.properties.name == old_name) {\n\t\t\tthis.properties.name = new_name;\n\t\t}\n\t};\n\n\tLGraphTexture.prototype.onDrawBackground = function(ctx) {\n\t\tif (this.flags.collapsed || this.size[1] <= 20) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this._drop_texture && ctx.webgl) {\n\t\t\tctx.drawImage(\n\t\t\t\tthis._drop_texture,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\tthis.size[0],\n\t\t\t\tthis.size[1]\n\t\t\t);\n\t\t\t//this._drop_texture.renderQuad(this.pos[0],this.pos[1],this.size[0],this.size[1]);\n\t\t\treturn;\n\t\t}\n\n\t\t//Different texture? then get it from the GPU\n\t\tif (this._last_preview_tex != this._last_tex) {\n\t\t\tif (ctx.webgl) {\n\t\t\t\tthis._canvas = this._last_tex;\n\t\t\t} else {\n\t\t\t\tvar tex_canvas = LGraphTexture.generateLowResTexturePreview(\n\t\t\t\t\tthis._last_tex\n\t\t\t\t);\n\t\t\t\tif (!tex_canvas) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis._last_preview_tex = this._last_tex;\n\t\t\t\tthis._canvas = cloneCanvas(tex_canvas);\n\t\t\t}\n\t\t}\n\n\t\tif (!this._canvas) {\n\t\t\treturn;\n\t\t}\n\n\t\t//render to graph canvas\n\t\tctx.save();\n\t\tif (!ctx.webgl) {\n\t\t\t//reverse image\n\t\t\tctx.translate(0, this.size[1]);\n\t\t\tctx.scale(1, -1);\n\t\t}\n\t\tctx.drawImage(this._canvas, 0, 0, this.size[0], this.size[1]);\n\t\tctx.restore();\n\t};\n\n\t//very slow, used at your own risk\n\tLGraphTexture.generateLowResTexturePreview = function(tex) {\n\t\tif (!tex) {\n\t\t\treturn null;\n\t\t}\n\n\t\tvar size = LGraphTexture.image_preview_size;\n\t\tvar temp_tex = tex;\n\n\t\tif (tex.format == gl.DEPTH_COMPONENT) {\n\t\t\treturn null;\n\t\t} //cannot generate from depth\n\n\t\t//Generate low-level version in the GPU to speed up\n\t\tif (tex.width > size || tex.height > size) {\n\t\t\ttemp_tex = this._preview_temp_tex;\n\t\t\tif (!this._preview_temp_tex) {\n\t\t\t\ttemp_tex = new GL.Texture(size, size, {\n\t\t\t\t\tminFilter: gl.NEAREST\n\t\t\t\t});\n\t\t\t\tthis._preview_temp_tex = temp_tex;\n\t\t\t}\n\n\t\t\t//copy\n\t\t\ttex.copyTo(temp_tex);\n\t\t\ttex = temp_tex;\n\t\t}\n\n\t\t//create intermediate canvas with lowquality version\n\t\tvar tex_canvas = this._preview_canvas;\n\t\tif (!tex_canvas) {\n\t\t\ttex_canvas = createCanvas(size, size);\n\t\t\tthis._preview_canvas = tex_canvas;\n\t\t}\n\n\t\tif (temp_tex) {\n\t\t\ttemp_tex.toCanvas(tex_canvas);\n\t\t}\n\t\treturn tex_canvas;\n\t};\n\n\tLGraphTexture.prototype.getResources = function(res) {\n\t\tif(this.properties.name)\n\t\t\tres[this.properties.name] = GL.Texture;\n\t\treturn res;\n\t};\n\n\tLGraphTexture.prototype.onGetInputs = function() {\n\t\treturn [[\"in\", \"Texture\"]];\n\t};\n\n\tLGraphTexture.prototype.onGetOutputs = function() {\n\t\treturn [\n\t\t\t[\"width\", \"number\"],\n\t\t\t[\"height\", \"number\"],\n\t\t\t[\"aspect\", \"number\"]\n\t\t];\n\t};\n\n\t//used to replace shader code\n\tLGraphTexture.replaceCode = function( code, context )\n\t{\n\t\treturn code.replace(/\\{\\{[a-zA-Z0-9_]*\\}\\}/g, function(v){\n\t\t\tv = v.replace( /[\\{\\}]/g, \"\" );\n\t\t\treturn context[v] || \"\";\n\t\t});\n\t}\n\n\tLiteGraph.registerNodeType(\"texture/texture\", LGraphTexture);\n\n\t//**************************\n\tfunction LGraphTexturePreview() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.properties = { flipY: false };\n\t\tthis.size = [\n\t\t\tLGraphTexture.image_preview_size,\n\t\t\tLGraphTexture.image_preview_size\n\t\t];\n\t}\n\n\tLGraphTexturePreview.title = \"Preview\";\n\tLGraphTexturePreview.desc = \"Show a texture in the graph canvas\";\n\tLGraphTexturePreview.allow_preview = false;\n\n\tLGraphTexturePreview.prototype.onDrawBackground = function(ctx) {\n\t\tif (this.flags.collapsed) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!ctx.webgl && !LGraphTexturePreview.allow_preview) {\n\t\t\treturn;\n\t\t} //not working well\n\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar tex_canvas = null;\n\n\t\tif (!tex.handle && ctx.webgl) {\n\t\t\ttex_canvas = tex;\n\t\t} else {\n\t\t\ttex_canvas = LGraphTexture.generateLowResTexturePreview(tex);\n\t\t}\n\n\t\t//render to graph canvas\n\t\tctx.save();\n\t\tif (this.properties.flipY) {\n\t\t\tctx.translate(0, this.size[1]);\n\t\t\tctx.scale(1, -1);\n\t\t}\n\t\tctx.drawImage(tex_canvas, 0, 0, this.size[0], this.size[1]);\n\t\tctx.restore();\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/preview\", LGraphTexturePreview);\n\n\t//**************************************\n\n\tfunction LGraphTextureSave() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"tex\", \"Texture\");\n\t\tthis.addOutput(\"name\", \"string\");\n\t\tthis.properties = { name: \"\", generate_mipmaps: false };\n\t}\n\n\tLGraphTextureSave.title = \"Save\";\n\tLGraphTextureSave.desc = \"Save a texture in the repository\";\n\n\tLGraphTextureSave.prototype.getPreviewTexture = function()\n\t{\n\t\treturn this._texture;\n\t}\n\n\tLGraphTextureSave.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.properties.generate_mipmaps) {\n\t\t\ttex.bind(0);\n\t\t\ttex.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR );\n\t\t\tgl.generateMipmap(tex.texture_type);\n\t\t\ttex.unbind(0);\n\t\t}\n\n\t\tif (this.properties.name) {\n\t\t\t//for cases where we want to perform something when storing it\n\t\t\tif (LGraphTexture.storeTexture) {\n\t\t\t\tLGraphTexture.storeTexture(this.properties.name, tex);\n\t\t\t} else {\n\t\t\t\tvar container = LGraphTexture.getTexturesContainer();\n\t\t\t\tcontainer[this.properties.name] = tex;\n\t\t\t}\n\t\t}\n\n\t\tthis._texture = tex;\n\t\tthis.setOutputData(0, tex);\n\t\tthis.setOutputData(1, this.properties.name);\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/save\", LGraphTextureSave);\n\n\t//****************************************************\n\n\tfunction LGraphTextureOperation() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addInput(\"TextureB\", \"Texture\");\n\t\tthis.addInput(\"value\", \"number\");\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\t\tthis.help = \"<p>pixelcode must be vec3, uvcode must be vec2, is optional</p>\\\n\t\t<p><strong>uv:</strong> tex. coords</p><p><strong>color:</strong> texture <strong>colorB:</strong> textureB</p><p><strong>time:</strong> scene time <strong>value:</strong> input value</p><p>For multiline you must type: result = ...</p>\";\n\n\t\tthis.properties = {\n\t\t\tvalue: 1,\n\t\t\tpixelcode: \"color + colorB * value\",\n\t\t\tuvcode: \"\",\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\n\t\tthis.has_error = false;\n\t}\n\n\tLGraphTextureOperation.widgets_info = {\n\t\tuvcode: { widget: \"code\" },\n\t\tpixelcode: { widget: \"code\" },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureOperation.title = \"Operation\";\n\tLGraphTextureOperation.desc = \"Texture shader operation\";\n\n\tLGraphTextureOperation.presets = {};\n\n\tLGraphTextureOperation.prototype.getExtraMenuOptions = function(\n\t\tgraphcanvas\n\t) {\n\t\tvar that = this;\n\t\tvar txt = !that.properties.show ? \"Show Texture\" : \"Hide Texture\";\n\t\treturn [\n\t\t\t{\n\t\t\t\tcontent: txt,\n\t\t\t\tcallback: function() {\n\t\t\t\t\tthat.properties.show = !that.properties.show;\n\t\t\t\t}\n\t\t\t}\n\t\t];\n\t};\n\n\tLGraphTextureOperation.prototype.onPropertyChanged = function()\n\t{\n\t\tthis.has_error = false;\n\t}\n\n\tLGraphTextureOperation.prototype.onDrawBackground = function(ctx) {\n\t\tif (\n\t\t\tthis.flags.collapsed ||\n\t\t\tthis.size[1] <= 20 ||\n\t\t\t!this.properties.show\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this._tex) {\n\t\t\treturn;\n\t\t}\n\n\t\t//only works if using a webgl renderer\n\t\tif (this._tex.gl != ctx) {\n\t\t\treturn;\n\t\t}\n\n\t\t//render to graph canvas\n\t\tctx.save();\n\t\tctx.drawImage(this._tex, 0, 0, this.size[0], this.size[1]);\n\t\tctx.restore();\n\t};\n\n\tLGraphTextureOperation.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar texB = this.getInputData(1);\n\n\t\tif (!this.properties.uvcode && !this.properties.pixelcode) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = 512;\n\t\tvar height = 512;\n\t\tif (tex) {\n\t\t\twidth = tex.width;\n\t\t\theight = tex.height;\n\t\t} else if (texB) {\n\t\t\twidth = texB.width;\n\t\t\theight = texB.height;\n\t\t}\n\n\t\tif(!texB)\n\t\t\ttexB = GL.Texture.getWhiteTexture();\n\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision, tex );\n\n\t\tif (!tex && !this._tex) {\n\t\t\tthis._tex = new GL.Texture(width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });\n\t\t} else {\n\t\t\tthis._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision );\n\t\t}\n\n\t\tvar uvcode = \"\";\n\t\tif (this.properties.uvcode) {\n\t\t\tuvcode = \"uv = \" + this.properties.uvcode;\n\t\t\tif (this.properties.uvcode.indexOf(\";\") != -1) {\n\t\t\t\t//there are line breaks, means multiline code\n\t\t\t\tuvcode = this.properties.uvcode;\n\t\t\t}\n\t\t}\n\n\t\tvar pixelcode = \"\";\n\t\tif (this.properties.pixelcode) {\n\t\t\tpixelcode = \"result = \" + this.properties.pixelcode;\n\t\t\tif (this.properties.pixelcode.indexOf(\";\") != -1) {\n\t\t\t\t//there are line breaks, means multiline code\n\t\t\t\tpixelcode = this.properties.pixelcode;\n\t\t\t}\n\t\t}\n\n\t\tvar shader = this._shader;\n\n\t\tif ( !this.has_error && (!shader || this._shader_code != uvcode + \"|\" + pixelcode) ) {\n\n\t\t\tvar final_pixel_code = LGraphTexture.replaceCode( LGraphTextureOperation.pixel_shader, { UV_CODE:uvcode, PIXEL_CODE:pixelcode });\n\n\t\t\ttry {\n\t\t\t\tshader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, final_pixel_code );\n\t\t\t\tthis.boxcolor = \"#00FF00\";\n\t\t\t} catch (err) {\n\t\t\t\t//console.log(\"Error compiling shader: \", err, final_pixel_code );\n\t\t\t\tGL.Shader.dumpErrorToConsole(err,Shader.SCREEN_VERTEX_SHADER, final_pixel_code);\n\t\t\t\tthis.boxcolor = \"#FF0000\";\n\t\t\t\tthis.has_error = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis._shader = shader;\n\t\t\tthis._shader_code = uvcode + \"|\" + pixelcode;\n\t\t}\n\n\t\tif(!this._shader)\n\t\t\treturn;\n\n\t\tvar value = this.getInputData(2);\n\t\tif (value != null) {\n\t\t\tthis.properties.value = value;\n\t\t} else {\n\t\t\tvalue = parseFloat(this.properties.value);\n\t\t}\n\n\t\tvar time = this.graph.getTime();\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\tgl.disable(gl.CULL_FACE);\n\t\t\tgl.disable(gl.BLEND);\n\t\t\tif (tex) {\n\t\t\t\ttex.bind(0);\n\t\t\t}\n\t\t\tif (texB) {\n\t\t\t\ttexB.bind(1);\n\t\t\t}\n\t\t\tvar mesh = Mesh.getScreenQuad();\n\t\t\tshader\n\t\t\t\t.uniforms({\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tu_textureB: 1,\n\t\t\t\t\tvalue: value,\n\t\t\t\t\ttexSize: [width, height,1/width,1/height],\n\t\t\t\t\ttime: time\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureOperation.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\t\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform vec4 texSize;\\n\\\n\t\tuniform float time;\\n\\\n\t\tuniform float value;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec2 uv = v_coord;\\n\\\n\t\t\t{{UV_CODE}};\\n\\\n\t\t\tvec4 color4 = texture2D(u_texture, uv);\\n\\\n\t\t\tvec3 color = color4.rgb;\\n\\\n\t\t\tvec4 color4B = texture2D(u_textureB, uv);\\n\\\n\t\t\tvec3 colorB = color4B.rgb;\\n\\\n\t\t\tvec3 result = color;\\n\\\n\t\t\tfloat alpha = 1.0;\\n\\\n\t\t\t{{PIXEL_CODE}};\\n\\\n\t\t\tgl_FragColor = vec4(result, alpha);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLGraphTextureOperation.registerPreset = function ( name, code )\n\t{\n\t\tLGraphTextureOperation.presets[name] = code;\n\t}\n\n\tLGraphTextureOperation.registerPreset(\"\",\"\");\n\tLGraphTextureOperation.registerPreset(\"bypass\",\"color\");\n\tLGraphTextureOperation.registerPreset(\"add\",\"color + colorB * value\");\n\tLGraphTextureOperation.registerPreset(\"substract\",\"(color - colorB) * value\");\n\tLGraphTextureOperation.registerPreset(\"mate\",\"mix( color, colorB, color4B.a * value)\");\n\tLGraphTextureOperation.registerPreset(\"invert\",\"vec3(1.0) - color\");\n\tLGraphTextureOperation.registerPreset(\"multiply\",\"color * colorB * value\");\n\tLGraphTextureOperation.registerPreset(\"divide\",\"(color / colorB) / value\");\n\tLGraphTextureOperation.registerPreset(\"difference\",\"abs(color - colorB) * value\");\n\tLGraphTextureOperation.registerPreset(\"max\",\"max(color, colorB) * value\");\n\tLGraphTextureOperation.registerPreset(\"min\",\"min(color, colorB) * value\");\n\tLGraphTextureOperation.registerPreset(\"displace\",\"texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz\");\n\tLGraphTextureOperation.registerPreset(\"grayscale\",\"vec3(color.x + color.y + color.z) * value / 3.0\");\n\tLGraphTextureOperation.registerPreset(\"saturation\",\"mix( vec3(color.x + color.y + color.z) / 3.0, color, value )\");\n\tLGraphTextureOperation.registerPreset(\"normalmap\",\"\\n\\\n\t\tfloat z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\\n\\\n\t\tfloat z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\\n\\\n\t\tfloat z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\\n\\\n\t\tfloat z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\\n\\\n\t\tfloat z4 = color.x;\\n\\\n\t\tfloat z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\\n\\\n\t\tfloat z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\\n\\\n\t\tfloat z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\\n\\\n\t\tfloat z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\\n\\\n\t\tvec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\\n\\\n\t\tnormal.xy *= value;\\n\\\n\t\tresult.xyz = normalize(normal) * 0.5 + vec3(0.5);\\n\\\n\t\");\n\tLGraphTextureOperation.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)\");\n\n\t//webglstudio stuff...\n\tLGraphTextureOperation.prototype.onInspect = function(widgets)\n\t{\n\t\tvar that = this;\n\t\twidgets.addCombo(\"Presets\",\"\",{ values: Object.keys(LGraphTextureOperation.presets), callback: function(v){\n\t\t\tvar code = LGraphTextureOperation.presets[v];\n\t\t\tif(!code)\n\t\t\t\treturn;\n\t\t\tthat.setProperty(\"pixelcode\",code);\n\t\t\tthat.title = v;\n\t\t\twidgets.refresh();\n\t\t}});\n\t}\n\n\tLiteGraph.registerNodeType(\"texture/operation\", LGraphTextureOperation);\n\n\t//****************************************************\n\n\tfunction LGraphTextureShader() {\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tcode: \"\",\n\t\t\tu_value: 1,\n\t\t\tu_color: [1,1,1,1],\n\t\t\twidth: 512,\n\t\t\theight: 512,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\n\t\tthis.properties.code = LGraphTextureShader.pixel_shader;\n\t\tthis._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec4.create(), time: 0 };\n\t}\n\n\tLGraphTextureShader.title = \"Shader\";\n\tLGraphTextureShader.desc = \"Texture shader\";\n\tLGraphTextureShader.widgets_info = {\n\t\tcode: { type: \"code\", lang: \"glsl\" },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureShader.prototype.onPropertyChanged = function(\n\t\tname,\n\t\tvalue\n\t) {\n\t\tif (name != \"code\") {\n\t\t\treturn;\n\t\t}\n\n\t\tvar shader = this.getShader();\n\t\tif (!shader) {\n\t\t\treturn;\n\t\t}\n\n\t\t//update connections\n\t\tvar uniforms = shader.uniformInfo;\n\n\t\t//remove deprecated slots\n\t\tif (this.inputs) {\n\t\t\tvar already = {};\n\t\t\tfor (var i = 0; i < this.inputs.length; ++i) {\n\t\t\t\tvar info = this.getInputInfo(i);\n\t\t\t\tif (!info) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (uniforms[info.name] && !already[info.name]) {\n\t\t\t\t\talready[info.name] = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthis.removeInput(i);\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\n\t\t//update existing ones\n\t\tfor (var i in uniforms) {\n\t\t\tvar info = shader.uniformInfo[i];\n\t\t\tif (info.loc === null) {\n\t\t\t\tcontinue;\n\t\t\t} //is an attribute, not a uniform\n\t\t\tif (i == \"time\") {\n\t\t\t\t//default one\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar type = \"number\";\n\t\t\tif (this._shader.samplers[i]) {\n\t\t\t\ttype = \"texture\";\n\t\t\t} else {\n\t\t\t\tswitch (info.size) {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\ttype = \"number\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\ttype = \"vec2\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\ttype = \"vec3\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\ttype = \"vec4\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 9:\n\t\t\t\t\t\ttype = \"mat3\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 16:\n\t\t\t\t\t\ttype = \"mat4\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar slot = this.findInputSlot(i);\n\t\t\tif (slot == -1) {\n\t\t\t\tthis.addInput(i, type);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar input_info = this.getInputInfo(slot);\n\t\t\tif (!input_info) {\n\t\t\t\tthis.addInput(i, type);\n\t\t\t} else {\n\t\t\t\tif (input_info.type == type) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthis.removeInput(slot, type);\n\t\t\t\tthis.addInput(i, type);\n\t\t\t}\n\t\t}\n\t};\n\n\tLGraphTextureShader.prototype.getShader = function() {\n\t\t//replug\n\t\tif (this._shader && this._shader_code == this.properties.code) {\n\t\t\treturn this._shader;\n\t\t}\n\n\t\tthis._shader_code = this.properties.code;\n\t\tthis._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, this.properties.code );\n\t\tif (!this._shader) {\n\t\t\tthis.boxcolor = \"red\";\n\t\t\treturn null;\n\t\t} else {\n\t\t\tthis.boxcolor = \"green\";\n\t\t}\n\t\treturn this._shader;\n\t};\n\n\tLGraphTextureShader.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar shader = this.getShader();\n\t\tif (!shader) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar tex_slot = 0;\n\t\tvar in_tex = null;\n\n\t\t//set uniforms\n\t\tif(this.inputs)\n\t\tfor (var i = 0; i < this.inputs.length; ++i) {\n\t\t\tvar info = this.getInputInfo(i);\n\t\t\tvar data = this.getInputData(i);\n\t\t\tif (data == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (data.constructor === GL.Texture) {\n\t\t\t\tdata.bind(tex_slot);\n\t\t\t\tif (!in_tex) {\n\t\t\t\t\tin_tex = data;\n\t\t\t\t}\n\t\t\t\tdata = tex_slot;\n\t\t\t\ttex_slot++;\n\t\t\t}\n\t\t\tshader.setUniform(info.name, data); //data is tex_slot\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision, in_tex );\n\n\t\t//render to texture\n\t\tvar w = this.properties.width | 0;\n\t\tvar h = this.properties.height | 0;\n\t\tif (w == 0) {\n\t\t\tw = in_tex ? in_tex.width : gl.canvas.width;\n\t\t}\n\t\tif (h == 0) {\n\t\t\th = in_tex ? in_tex.height : gl.canvas.height;\n\t\t}\n\t\tuniforms.texSize[0] = w;\n\t\tuniforms.texSize[1] = h;\n\t\tuniforms.texSize[2] = 1/w;\n\t\tuniforms.texSize[3] = 1/h;\n\t\tuniforms.time = this.graph.getTime();\n\t\tuniforms.u_value = this.properties.u_value;\n\t\tuniforms.u_color.set( this.properties.u_color );\n\n\t\tif ( !this._tex || this._tex.type != type ||  this._tex.width != w || this._tex.height != h ) {\n\t\t\tthis._tex = new GL.Texture(w, h, {  type: type, format: gl.RGBA, filter: gl.LINEAR });\n\t\t}\n\t\tvar tex = this._tex;\n\t\ttex.drawTo(function() {\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureShader.pixel_shader =\n\"precision highp float;\\n\\\n\\n\\\nvarying vec2 v_coord;\\n\\\nuniform float time; //time in seconds\\n\\\nuniform vec4 texSize; //tex resolution\\n\\\nuniform float u_value;\\n\\\nuniform vec4 u_color;\\n\\n\\\nvoid main() {\\n\\\n\tvec2 uv = v_coord;\\n\\\n\tvec3 color = vec3(0.0);\\n\\\n\t//your code here\\n\\\n\tcolor.xy=uv;\\n\\n\\\n\tgl_FragColor = vec4(color, 1.0);\\n\\\n}\\n\\\n\";\n\n\tLiteGraph.registerNodeType(\"texture/shader\", LGraphTextureShader);\n\n\t// Texture Scale Offset\n\n\tfunction LGraphTextureScaleOffset() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"scale\", \"vec2\");\n\t\tthis.addInput(\"offset\", \"vec2\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\toffset: vec2.fromValues(0, 0),\n\t\t\tscale: vec2.fromValues(1, 1),\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureScaleOffset.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureScaleOffset.title = \"Scale/Offset\";\n\tLGraphTextureScaleOffset.desc = \"Applies an scaling and offseting\";\n\n\tLGraphTextureScaleOffset.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (!this.isOutputConnected(0) || !tex) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = tex.width;\n\t\tvar height = tex.height;\n\t\tvar type =  this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT;\n\t\tif (this.precision === LGraphTexture.DEFAULT) {\n\t\t\ttype = tex.type;\n\t\t}\n\n\t\tif (\n\t\t\t!this._tex ||\n\t\t\tthis._tex.width != width ||\n\t\t\tthis._tex.height != height ||\n\t\t\tthis._tex.type != type\n\t\t) {\n\t\t\tthis._tex = new GL.Texture(width, height, {\n\t\t\t\ttype: type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tvar shader = this._shader;\n\n\t\tif (!shader) {\n\t\t\tshader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureScaleOffset.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar scale = this.getInputData(1);\n\t\tif (scale) {\n\t\t\tthis.properties.scale[0] = scale[0];\n\t\t\tthis.properties.scale[1] = scale[1];\n\t\t} else {\n\t\t\tscale = this.properties.scale;\n\t\t}\n\n\t\tvar offset = this.getInputData(2);\n\t\tif (offset) {\n\t\t\tthis.properties.offset[0] = offset[0];\n\t\t\tthis.properties.offset[1] = offset[1];\n\t\t} else {\n\t\t\toffset = this.properties.offset;\n\t\t}\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\tgl.disable(gl.CULL_FACE);\n\t\t\tgl.disable(gl.BLEND);\n\t\t\ttex.bind(0);\n\t\t\tvar mesh = Mesh.getScreenQuad();\n\t\t\tshader\n\t\t\t\t.uniforms({\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tu_scale: scale,\n\t\t\t\t\tu_offset: offset\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureScaleOffset.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\t\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform vec2 u_scale;\\n\\\n\t\tuniform vec2 u_offset;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec2 uv = v_coord;\\n\\\n\t\t\tuv = uv / u_scale - u_offset;\\n\\\n\t\t\tgl_FragColor = texture2D(u_texture, uv);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/scaleOffset\",\n\t\tLGraphTextureScaleOffset\n\t);\n\n\t// Warp (distort a texture) *************************\n\n\tfunction LGraphTextureWarp() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"warp\", \"Texture\");\n\t\tthis.addInput(\"factor\", \"number\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tfactor: 0.01,\n\t\t\tscale: [1,1],\n\t\t\toffset: [0,0],\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\n\t\tthis._uniforms = { \n\t\t\tu_texture: 0, \n\t\t\tu_textureB: 1, \n\t\t\tu_factor: 1, \n\t\t\tu_scale: vec2.create(),\n\t\t\tu_offset: vec2.create()\n\t\t};\n\t}\n\n\tLGraphTextureWarp.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureWarp.title = \"Warp\";\n\tLGraphTextureWarp.desc = \"Texture warp operation\";\n\n\tLGraphTextureWarp.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar texB = this.getInputData(1);\n\n\t\tvar width = 512;\n\t\tvar height = 512;\n\t\tvar type = gl.UNSIGNED_BYTE;\n\t\tif (tex) {\n\t\t\twidth = tex.width;\n\t\t\theight = tex.height;\n\t\t\ttype = tex.type;\n\t\t} else if (texB) {\n\t\t\twidth = texB.width;\n\t\t\theight = texB.height;\n\t\t\ttype = texB.type;\n\t\t}\n\n\t\tif (!tex && !this._tex) {\n\t\t\tthis._tex = new GL.Texture(width, height, {\n\t\t\t\ttype:\n\t\t\t\t\tthis.precision === LGraphTexture.LOW\n\t\t\t\t\t\t? gl.UNSIGNED_BYTE\n\t\t\t\t\t\t: gl.HIGH_PRECISION_FORMAT,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t} else {\n\t\t\tthis._tex = LGraphTexture.getTargetTexture(\n\t\t\t\ttex || this._tex,\n\t\t\t\tthis._tex,\n\t\t\t\tthis.properties.precision\n\t\t\t);\n\t\t}\n\n\t\tvar shader = this._shader;\n\n\t\tif (!shader) {\n\t\t\tshader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureWarp.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar factor = this.getInputData(2);\n\t\tif (factor != null) {\n\t\t\tthis.properties.factor = factor;\n\t\t} else {\n\t\t\tfactor = parseFloat(this.properties.factor);\n\t\t}\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_factor = factor;\n\t\tuniforms.u_scale.set( this.properties.scale );\n\t\tuniforms.u_offset.set( this.properties.offset );\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\tgl.disable(gl.CULL_FACE);\n\t\t\tgl.disable(gl.BLEND);\n\t\t\tif (tex) {\n\t\t\t\ttex.bind(0);\n\t\t\t}\n\t\t\tif (texB) {\n\t\t\t\ttexB.bind(1);\n\t\t\t}\n\t\t\tvar mesh = Mesh.getScreenQuad();\n\t\t\tshader\n\t\t\t\t.uniforms( uniforms )\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureWarp.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\t\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform float u_factor;\\n\\\n\t\tuniform vec2 u_scale;\\n\\\n\t\tuniform vec2 u_offset;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec2 uv = v_coord;\\n\\\n\t\t\tuv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor * u_scale + u_offset;\\n\\\n\t\t\tgl_FragColor = texture2D(u_texture, uv);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/warp\", LGraphTextureWarp);\n\n\t//****************************************************\n\n\t// Texture to Viewport *****************************************\n\tfunction LGraphTextureToViewport() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tadditive: false,\n\t\t\tantialiasing: false,\n\t\t\tfilter: true,\n\t\t\tdisable_alpha: false,\n\t\t\tgamma: 1.0,\n\t\t\tviewport: [0,0,1,1]\n\t\t};\n\t\tthis.size[0] = 130;\n\t}\n\n\tLGraphTextureToViewport.title = \"to Viewport\";\n\tLGraphTextureToViewport.desc = \"Texture to viewport\";\n\n\tLGraphTextureToViewport._prev_viewport = new Float32Array(4);\n\n\tLGraphTextureToViewport.prototype.onDrawBackground = function( ctx )\n\t{\n\t\tif ( this.flags.collapsed || this.size[1] <= 40 )\n\t\t\treturn;\n\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tctx.drawImage( ctx == gl ? tex : gl.canvas, 10,30, this.size[0] -20, this.size[1] -40);\n\t}\n\n\tLGraphTextureToViewport.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.properties.disable_alpha) {\n\t\t\tgl.disable(gl.BLEND);\n\t\t} else {\n\t\t\tgl.enable(gl.BLEND);\n\t\t\tif (this.properties.additive) {\n\t\t\t\tgl.blendFunc(gl.SRC_ALPHA, gl.ONE);\n\t\t\t} else {\n\t\t\t\tgl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n\t\t\t}\n\t\t}\n\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tvar gamma = this.properties.gamma || 1.0;\n\t\tif (this.isInputConnected(1)) {\n\t\t\tgamma = this.getInputData(1);\n\t\t}\n\n\t\ttex.setParameter(\n\t\t\tgl.TEXTURE_MAG_FILTER,\n\t\t\tthis.properties.filter ? gl.LINEAR : gl.NEAREST\n\t\t);\n\n\t\tvar old_viewport = LGraphTextureToViewport._prev_viewport;\n\t\told_viewport.set( gl.viewport_data );\n\t\tvar new_view = this.properties.viewport;\n\t\tgl.viewport( old_viewport[0] + old_viewport[2] * new_view[0], old_viewport[1] + old_viewport[3] * new_view[1], old_viewport[2] * new_view[2], old_viewport[3] * new_view[3] );\n\t\tvar viewport = gl.getViewport(); //gl.getParameter(gl.VIEWPORT);\n\n\t\tif (this.properties.antialiasing) {\n\t\t\tif (!LGraphTextureToViewport._shader) {\n\t\t\t\tLGraphTextureToViewport._shader = new GL.Shader(\n\t\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\tLGraphTextureToViewport.aa_pixel_shader\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tvar mesh = Mesh.getScreenQuad();\n\t\t\ttex.bind(0);\n\t\t\tLGraphTextureToViewport._shader\n\t\t\t\t.uniforms({\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tuViewportSize: [tex.width, tex.height],\n\t\t\t\t\tu_igamma: 1 / gamma,\n\t\t\t\t\tinverseVP: [1 / tex.width, 1 / tex.height]\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t} else {\n\t\t\tif (gamma != 1.0) {\n\t\t\t\tif (!LGraphTextureToViewport._gamma_shader) {\n\t\t\t\t\tLGraphTextureToViewport._gamma_shader = new GL.Shader(\n\t\t\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\t\tLGraphTextureToViewport.gamma_pixel_shader\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\ttex.toViewport(LGraphTextureToViewport._gamma_shader, {\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tu_igamma: 1 / gamma\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\ttex.toViewport();\n\t\t\t}\n\t\t}\n\n\t\tgl.viewport( old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3] );\n\t};\n\n\tLGraphTextureToViewport.prototype.onGetInputs = function() {\n\t\treturn [[\"gamma\", \"number\"]];\n\t};\n\n\tLGraphTextureToViewport.aa_pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 uViewportSize;\\n\\\n\t\tuniform vec2 inverseVP;\\n\\\n\t\tuniform float u_igamma;\\n\\\n\t\t#define FXAA_REDUCE_MIN   (1.0/ 128.0)\\n\\\n\t\t#define FXAA_REDUCE_MUL   (1.0 / 8.0)\\n\\\n\t\t#define FXAA_SPAN_MAX     8.0\\n\\\n\t\t\\n\\\n\t\t/* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\\n\\\n\t\tvec4 applyFXAA(sampler2D tex, vec2 fragCoord)\\n\\\n\t\t{\\n\\\n\t\t\tvec4 color = vec4(0.0);\\n\\\n\t\t\t/*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\\n\\\n\t\t\tvec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\\n\\\n\t\t\tvec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\\n\\\n\t\t\tvec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\\n\\\n\t\t\tvec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\\n\\\n\t\t\tvec3 rgbM  = texture2D(tex, fragCoord  * inverseVP).xyz;\\n\\\n\t\t\tvec3 luma = vec3(0.299, 0.587, 0.114);\\n\\\n\t\t\tfloat lumaNW = dot(rgbNW, luma);\\n\\\n\t\t\tfloat lumaNE = dot(rgbNE, luma);\\n\\\n\t\t\tfloat lumaSW = dot(rgbSW, luma);\\n\\\n\t\t\tfloat lumaSE = dot(rgbSE, luma);\\n\\\n\t\t\tfloat lumaM  = dot(rgbM,  luma);\\n\\\n\t\t\tfloat lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\\n\\\n\t\t\tfloat lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\\n\\\n\t\t\t\\n\\\n\t\t\tvec2 dir;\\n\\\n\t\t\tdir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\\n\\\n\t\t\tdir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));\\n\\\n\t\t\t\\n\\\n\t\t\tfloat dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\\n\\\n\t\t\t\\n\\\n\t\t\tfloat rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\\n\\\n\t\t\tdir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\\n\\\n\t\t\t\\n\\\n\t\t\tvec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \\n\\\n\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\\n\\\n\t\t\tvec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \\n\\\n\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\\n\\\n\t\t\t\\n\\\n\t\t\t//return vec4(rgbA,1.0);\\n\\\n\t\t\tfloat lumaB = dot(rgbB, luma);\\n\\\n\t\t\tif ((lumaB < lumaMin) || (lumaB > lumaMax))\\n\\\n\t\t\t\tcolor = vec4(rgbA, 1.0);\\n\\\n\t\t\telse\\n\\\n\t\t\t\tcolor = vec4(rgbB, 1.0);\\n\\\n\t\t\tif(u_igamma != 1.0)\\n\\\n\t\t\t\tcolor.xyz = pow( color.xyz, vec3(u_igamma) );\\n\\\n\t\t\treturn color;\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t   gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLGraphTextureToViewport.gamma_pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_igamma;\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = texture2D( u_texture, v_coord);\\n\\\n\t\t\tcolor.xyz = pow(color.xyz, vec3(u_igamma) );\\n\\\n\t\t   gl_FragColor = color;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/toviewport\",\n\t\tLGraphTextureToViewport\n\t);\n\n\t// Texture Copy *****************************************\n\tfunction LGraphTextureCopy() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tsize: 0,\n\t\t\tgenerate_mipmaps: false,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureCopy.title = \"Copy\";\n\tLGraphTextureCopy.desc = \"Copy Texture\";\n\tLGraphTextureCopy.widgets_info = {\n\t\tsize: {\n\t\t\twidget: \"combo\",\n\t\t\tvalues: [0, 32, 64, 128, 256, 512, 1024, 2048]\n\t\t},\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureCopy.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex && !this._temp_texture) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\t//copy the texture\n\t\tif (tex) {\n\t\t\tvar width = tex.width;\n\t\t\tvar height = tex.height;\n\n\t\t\tif (this.properties.size != 0) {\n\t\t\t\twidth = this.properties.size;\n\t\t\t\theight = this.properties.size;\n\t\t\t}\n\n\t\t\tvar temp = this._temp_texture;\n\n\t\t\tvar type = tex.type;\n\t\t\tif (this.properties.precision === LGraphTexture.LOW) {\n\t\t\t\ttype = gl.UNSIGNED_BYTE;\n\t\t\t} else if (this.properties.precision === LGraphTexture.HIGH) {\n\t\t\t\ttype = gl.HIGH_PRECISION_FORMAT;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\t!temp ||\n\t\t\t\ttemp.width != width ||\n\t\t\t\ttemp.height != height ||\n\t\t\t\ttemp.type != type\n\t\t\t) {\n\t\t\t\tvar minFilter = gl.LINEAR;\n\t\t\t\tif (\n\t\t\t\t\tthis.properties.generate_mipmaps &&\n\t\t\t\t\tisPowerOfTwo(width) &&\n\t\t\t\t\tisPowerOfTwo(height)\n\t\t\t\t) {\n\t\t\t\t\tminFilter = gl.LINEAR_MIPMAP_LINEAR;\n\t\t\t\t}\n\t\t\t\tthis._temp_texture = new GL.Texture(width, height, {\n\t\t\t\t\ttype: type,\n\t\t\t\t\tformat: gl.RGBA,\n\t\t\t\t\tminFilter: minFilter,\n\t\t\t\t\tmagFilter: gl.LINEAR\n\t\t\t\t});\n\t\t\t}\n\t\t\ttex.copyTo(this._temp_texture);\n\n\t\t\tif (this.properties.generate_mipmaps) {\n\t\t\t\tthis._temp_texture.bind(0);\n\t\t\t\tgl.generateMipmap(this._temp_texture.texture_type);\n\t\t\t\tthis._temp_texture.unbind(0);\n\t\t\t}\n\t\t}\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/copy\", LGraphTextureCopy);\n\n\t// Texture Downsample *****************************************\n\tfunction LGraphTextureDownsample() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = {\n\t\t\titerations: 1,\n\t\t\tgenerate_mipmaps: false,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureDownsample.title = \"Downsample\";\n\tLGraphTextureDownsample.desc = \"Downsample Texture\";\n\tLGraphTextureDownsample.widgets_info = {\n\t\titerations: { type: \"number\", step: 1, precision: 0, min: 0 },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureDownsample.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex && !this._temp_texture) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\t//we do not allow any texture different than texture 2D\n\t\tif (!tex || tex.texture_type !== GL.TEXTURE_2D) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.properties.iterations < 1) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar shader = LGraphTextureDownsample._shader;\n\t\tif (!shader) {\n\t\t\tLGraphTextureDownsample._shader = shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureDownsample.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar width = tex.width | 0;\n\t\tvar height = tex.height | 0;\n\t\tvar type = tex.type;\n\t\tif (this.properties.precision === LGraphTexture.LOW) {\n\t\t\ttype = gl.UNSIGNED_BYTE;\n\t\t} else if (this.properties.precision === LGraphTexture.HIGH) {\n\t\t\ttype = gl.HIGH_PRECISION_FORMAT;\n\t\t}\n\t\tvar iterations = this.properties.iterations || 1;\n\n\t\tvar origin = tex;\n\t\tvar target = null;\n\n\t\tvar temp = [];\n\t\tvar options = {\n\t\t\ttype: type,\n\t\t\tformat: tex.format\n\t\t};\n\n\t\tvar offset = vec2.create();\n\t\tvar uniforms = {\n\t\t\tu_offset: offset\n\t\t};\n\n\t\tif (this._texture) {\n\t\t\tGL.Texture.releaseTemporary(this._texture);\n\t\t}\n\n\t\tfor (var i = 0; i < iterations; ++i) {\n\t\t\toffset[0] = 1 / width;\n\t\t\toffset[1] = 1 / height;\n\t\t\twidth = width >> 1 || 0;\n\t\t\theight = height >> 1 || 0;\n\t\t\ttarget = GL.Texture.getTemporary(width, height, options);\n\t\t\ttemp.push(target);\n\t\t\torigin.setParameter(GL.TEXTURE_MAG_FILTER, GL.NEAREST);\n\t\t\torigin.copyTo(target, shader, uniforms);\n\t\t\tif (width == 1 && height == 1) {\n\t\t\t\tbreak;\n\t\t\t} //nothing else to do\n\t\t\torigin = target;\n\t\t}\n\n\t\t//keep the last texture used\n\t\tthis._texture = temp.pop();\n\n\t\t//free the rest\n\t\tfor (var i = 0; i < temp.length; ++i) {\n\t\t\tGL.Texture.releaseTemporary(temp[i]);\n\t\t}\n\n\t\tif (this.properties.generate_mipmaps) {\n\t\t\tthis._texture.bind(0);\n\t\t\tgl.generateMipmap(this._texture.texture_type);\n\t\t\tthis._texture.unbind(0);\n\t\t}\n\n\t\tthis.setOutputData(0, this._texture);\n\t};\n\n\tLGraphTextureDownsample.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 u_offset;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = texture2D(u_texture, v_coord );\\n\\\n\t\t\tcolor += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\\n\\\n\t\t\tcolor += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\\n\\\n\t\t\tcolor += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\\n\\\n\t\t   gl_FragColor = color * 0.25;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/downsample\",\n\t\tLGraphTextureDownsample\n\t);\n\n\n\n\tfunction LGraphTextureResize() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tsize: [512,512],\n\t\t\tgenerate_mipmaps: false,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureResize.title = \"Resize\";\n\tLGraphTextureResize.desc = \"Resize Texture\";\n\tLGraphTextureResize.widgets_info = {\n\t\titerations: { type: \"number\", step: 1, precision: 0, min: 0 },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureResize.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex && !this._temp_texture) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\t//we do not allow any texture different than texture 2D\n\t\tif (!tex || tex.texture_type !== GL.TEXTURE_2D) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = this.properties.size[0] | 0;\n\t\tvar height = this.properties.size[1] | 0;\n\t\tif(width == 0)\n\t\t\twidth = tex.width;\n\t\tif(height == 0)\n\t\t\theight = tex.height;\n\t\tvar type = tex.type;\n\t\tif (this.properties.precision === LGraphTexture.LOW) {\n\t\t\ttype = gl.UNSIGNED_BYTE;\n\t\t} else if (this.properties.precision === LGraphTexture.HIGH) {\n\t\t\ttype = gl.HIGH_PRECISION_FORMAT;\n\t\t}\n\n\t\tif( !this._texture || this._texture.width != width || this._texture.height != height || this._texture.type != type )\n\t\t\tthis._texture = new GL.Texture( width, height, { type: type } );\n\n\t\ttex.copyTo( this._texture );\n\n\t\tif (this.properties.generate_mipmaps) {\n\t\t\tthis._texture.bind(0);\n\t\t\tgl.generateMipmap(this._texture.texture_type);\n\t\t\tthis._texture.unbind(0);\n\t\t}\n\n\t\tthis.setOutputData(0, this._texture);\n\t};\n\n\tLiteGraph.registerNodeType( \"texture/resize\", LGraphTextureResize );\n\n\t// Texture Average  *****************************************\n\tfunction LGraphTextureAverage() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"tex\", \"Texture\");\n\t\tthis.addOutput(\"avg\", \"vec4\");\n\t\tthis.addOutput(\"lum\", \"number\");\n\t\tthis.properties = {\n\t\t\tuse_previous_frame: true, //to avoid stalls \n\t\t\thigh_quality: false //to use as much pixels as possible\n\t\t};\n\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_mipmap_offset: 0\n\t\t};\n\t\tthis._luminance = new Float32Array(4);\n\t}\n\n\tLGraphTextureAverage.title = \"Average\";\n\tLGraphTextureAverage.desc =\n\t\t\"Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture.\\n If high_quality is true, then it generates the mipmaps first and reads from the lower one.\";\n\n\tLGraphTextureAverage.prototype.onExecute = function() {\n\t\tif (!this.properties.use_previous_frame) {\n\t\t\tthis.updateAverage();\n\t\t}\n\n\t\tvar v = this._luminance;\n\t\tthis.setOutputData(0, this._temp_texture);\n\t\tthis.setOutputData(1, v);\n\t\tthis.setOutputData(2, (v[0] + v[1] + v[2]) / 3);\n\t};\n\n\t//executed before rendering the frame\n\tLGraphTextureAverage.prototype.onPreRenderExecute = function() {\n\t\tthis.updateAverage();\n\t};\n\n\tLGraphTextureAverage.prototype.updateAverage = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\t!this.isOutputConnected(0) &&\n\t\t\t!this.isOutputConnected(1) &&\n\t\t\t!this.isOutputConnected(2)\n\t\t) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (!LGraphTextureAverage._shader) {\n\t\t\tLGraphTextureAverage._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureAverage.pixel_shader\n\t\t\t);\n\t\t\t//creates 256 random numbers and stores them in two mat4\n\t\t\tvar samples = new Float32Array(16);\n\t\t\tfor (var i = 0; i < samples.length; ++i) {\n\t\t\t\tsamples[i] = Math.random(); //poorly distributed samples\n\t\t\t}\n\t\t\t//upload only once\n\t\t\tLGraphTextureAverage._shader.uniforms({\n\t\t\t\tu_samples_a: samples.subarray(0, 16),\n\t\t\t\tu_samples_b: samples.subarray(16, 32)\n\t\t\t});\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\t\tvar type = gl.UNSIGNED_BYTE;\n\t\tif (tex.type != type) {\n\t\t\t//force floats, half floats cannot be read with gl.readPixels\n\t\t\ttype = gl.FLOAT;\n\t\t}\n\n\t\tif (!temp || temp.type != type) {\n\t\t\tthis._temp_texture = new GL.Texture(1, 1, {\n\t\t\t\ttype: type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.NEAREST\n\t\t\t});\n\t\t}\n\n\t\tthis._uniforms.u_mipmap_offset = 0;\n\n\t\tif(this.properties.high_quality)\n\t\t{\n\t\t\tif( !this._temp_pot2_texture || this._temp_pot2_texture.type != type )\n\t\t\t\tthis._temp_pot2_texture = new GL.Texture(512, 512, {\n\t\t\t\t\ttype: type,\n\t\t\t\t\tformat: gl.RGBA,\n\t\t\t\t\tminFilter: gl.LINEAR_MIPMAP_LINEAR,\n\t\t\t\t\tmagFilter: gl.LINEAR\n\t\t\t\t});\n\n\t\t\ttex.copyTo( this._temp_pot2_texture );\n\t\t\ttex = this._temp_pot2_texture;\n\t\t\ttex.bind(0);\n\t\t\tgl.generateMipmap(GL.TEXTURE_2D);\n\t\t\tthis._uniforms.u_mipmap_offset = 9;\n\t\t}\n\n\t\tvar shader = LGraphTextureAverage._shader;\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_mipmap_offset = this.properties.mipmap_offset;\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tgl.disable(gl.BLEND);\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\ttex.toViewport(shader, uniforms);\n\t\t});\n\n\t\tif (this.isOutputConnected(1) || this.isOutputConnected(2)) {\n\t\t\tvar pixel = this._temp_texture.getPixels();\n\t\t\tif (pixel) {\n\t\t\t\tvar v = this._luminance;\n\t\t\t\tvar type = this._temp_texture.type;\n\t\t\t\tv.set(pixel);\n\t\t\t\tif (type == gl.UNSIGNED_BYTE) {\n\t\t\t\t\tvec4.scale(v, v, 1 / 255);\n\t\t\t\t} else if (\n\t\t\t\t\ttype == GL.HALF_FLOAT ||\n\t\t\t\t\ttype == GL.HALF_FLOAT_OES\n\t\t\t\t) {\n\t\t\t\t\t//no half floats possible, hard to read back unless copyed to a FLOAT texture, so temp_texture is always forced to FLOAT\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tLGraphTextureAverage.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform mat4 u_samples_a;\\n\\\n\t\tuniform mat4 u_samples_b;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_mipmap_offset;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = vec4(0.0);\\n\\\n\t\t\t//random average\\n\\\n\t\t\tfor(int i = 0; i < 4; ++i)\\n\\\n\t\t\t\tfor(int j = 0; j < 4; ++j)\\n\\\n\t\t\t\t{\\n\\\n\t\t\t\t\tcolor += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\\n\\\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\\\n\t\t\t\t}\\n\\\n\t\t   gl_FragColor = color * 0.03125;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/average\", LGraphTextureAverage);\n\n\n\n\t// Computes operation between pixels (max, min)  *****************************************\n\tfunction LGraphTextureMinMax() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"min_t\", \"Texture\");\n\t\tthis.addOutput(\"max_t\", \"Texture\");\n\t\tthis.addOutput(\"min\", \"vec4\");\n\t\tthis.addOutput(\"max\", \"vec4\");\n\t\tthis.properties = {\n\t\t\tmode: \"max\",\n\t\t\tuse_previous_frame: true //to avoid stalls \n\t\t};\n\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0\n\t\t};\n\n\t\tthis._max = new Float32Array(4);\n\t\tthis._min = new Float32Array(4);\n\n\t\tthis._textures_chain = [];\n\t}\n\n\tLGraphTextureMinMax.widgets_info = {\n\t\tmode: { widget: \"combo\", values: [\"min\",\"max\",\"avg\"] }\n\t};\n\n\tLGraphTextureMinMax.title = \"MinMax\";\n\tLGraphTextureMinMax.desc = \"Compute the scene min max\";\n\n\tLGraphTextureMinMax.prototype.onExecute = function() {\n\t\tif (!this.properties.use_previous_frame) {\n\t\t\tthis.update();\n\t\t}\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t\tthis.setOutputData(1, this._luminance);\n\t};\n\n\t//executed before rendering the frame\n\tLGraphTextureMinMax.prototype.onPreRenderExecute = function() {\n\t\tthis.update();\n\t};\n\n\tLGraphTextureMinMax.prototype.update = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !this.isOutputConnected(0) && !this.isOutputConnected(1) ) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (!LGraphTextureMinMax._shader) {\n\t\t\tLGraphTextureMinMax._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureMinMax.pixel_shader );\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\t\tvar type = gl.UNSIGNED_BYTE;\n\t\tif (tex.type != type) {\n\t\t\t//force floats, half floats cannot be read with gl.readPixels\n\t\t\ttype = gl.FLOAT;\n\t\t}\n\n\t\tvar size = 512;\n\n\t\tif( !this._textures_chain.length || this._textures_chain[0].type != type )\n\t\t{\n\t\t\tvar index = 0;\n\t\t\twhile(i)\n\t\t\t{\n\t\t\t\tthis._textures_chain[i] = new GL.Texture( size, size, {\n\t\t\t\t\ttype: type,\n\t\t\t\t\tformat: gl.RGBA,\n\t\t\t\t\tfilter: gl.NEAREST\n\t\t\t\t});\n\t\t\t\tsize = size >> 2;\n\t\t\t\ti++;\n\t\t\t\tif(size == 1)\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\ttex.copyTo( this._textures_chain[0] );\n\t\tvar prev = this._textures_chain[0];\n\t\tfor(var i = 1; i <= this._textures_chain.length; ++i)\n\t\t{\n\t\t\tvar tex = this._textures_chain[i];\n\n\t\t\tprev = tex;\t\t\t\t\n\t\t}\n\n\t\tvar shader = LGraphTextureMinMax._shader;\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_mipmap_offset = this.properties.mipmap_offset;\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tgl.disable(gl.BLEND);\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\ttex.toViewport(shader, uniforms);\n\t\t});\n\t};\n\n\tLGraphTextureMinMax.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform mat4 u_samples_a;\\n\\\n\t\tuniform mat4 u_samples_b;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_mipmap_offset;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = vec4(0.0);\\n\\\n\t\t\t//random average\\n\\\n\t\t\tfor(int i = 0; i < 4; ++i)\\n\\\n\t\t\t\tfor(int j = 0; j < 4; ++j)\\n\\\n\t\t\t\t{\\n\\\n\t\t\t\t\tcolor += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\\n\\\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\\\n\t\t\t\t}\\n\\\n\t\t   gl_FragColor = color * 0.03125;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\t//LiteGraph.registerNodeType(\"texture/clustered_operation\", LGraphTextureClusteredOperation);\n\n\n\tfunction LGraphTextureTemporalSmooth() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"factor\", \"Number\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = { factor: 0.5 };\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_textureB: 1,\n\t\t\tu_factor: this.properties.factor\n\t\t};\n\t}\n\n\tLGraphTextureTemporalSmooth.title = \"Smooth\";\n\tLGraphTextureTemporalSmooth.desc = \"Smooth texture over time\";\n\n\tLGraphTextureTemporalSmooth.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex || !this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!LGraphTextureTemporalSmooth._shader) {\n\t\t\tLGraphTextureTemporalSmooth._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureTemporalSmooth.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.type != tex.type ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height\n\t\t) {\n\t\t\tvar options = {\n\t\t\t\ttype: tex.type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.NEAREST\n\t\t\t};\n\t\t\tthis._temp_texture = new GL.Texture(tex.width, tex.height, options );\n\t\t\tthis._temp_texture2 = new GL.Texture(tex.width, tex.height, options );\n\t\t\ttex.copyTo(this._temp_texture2);\n\t\t}\n\n\t\tvar tempA = this._temp_texture;\n\t\tvar tempB = this._temp_texture2;\n\n\t\tvar shader = LGraphTextureTemporalSmooth._shader;\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_factor = 1.0 - this.getInputOrProperty(\"factor\");\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\ttempA.drawTo(function() {\n\t\t\ttempB.bind(1);\n\t\t\ttex.toViewport(shader, uniforms);\n\t\t});\n\n\t\tthis.setOutputData(0, tempA);\n\n\t\t//swap\n\t\tthis._temp_texture = tempB;\n\t\tthis._temp_texture2 = tempA;\n\t};\n\n\tLGraphTextureTemporalSmooth.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tuniform float u_factor;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tgl_FragColor = mix( texture2D( u_texture, v_coord ), texture2D( u_textureB, v_coord ), u_factor );\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType( \"texture/temporal_smooth\", LGraphTextureTemporalSmooth );\n\n\n\tfunction LGraphTextureLinearAvgSmooth() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addOutput(\"avg\", \"Texture\");\n\t\tthis.addOutput(\"array\", \"Texture\");\n\t\tthis.properties = { samples: 64, frames_interval: 1 };\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_textureB: 1,\n\t\t\tu_samples: this.properties.samples,\n\t\t\tu_isamples: 1/this.properties.samples\n\t\t};\n\t\tthis.frame = 0;\n\t}\n\n\tLGraphTextureLinearAvgSmooth.title = \"Lineal Avg Smooth\";\n\tLGraphTextureLinearAvgSmooth.desc = \"Smooth texture linearly over time\";\n\n\tLGraphTextureLinearAvgSmooth[\"@samples\"] = { type: \"number\", min: 1, max: 64, step: 1, precision: 1 };\n\n\tLGraphTextureLinearAvgSmooth.prototype.getPreviewTexture = function()\n\t{\n\t\treturn this._temp_texture2;\n\t}\n\n\tLGraphTextureLinearAvgSmooth.prototype.onExecute = function() {\n\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex || !this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!LGraphTextureLinearAvgSmooth._shader) {\n\t\t\tLGraphTextureLinearAvgSmooth._shader_copy = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearAvgSmooth.pixel_shader_copy );\n\t\t\tLGraphTextureLinearAvgSmooth._shader_avg = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearAvgSmooth.pixel_shader_avg );\n\t\t}\n\n\t\tvar samples = clamp(this.properties.samples,0,64);\n\t\tvar frame = this.frame;\n\t\tvar interval = this.properties.frames_interval;\n\n\t\tif( interval == 0 || frame % interval == 0 )\n\t\t{\n\t\t\tvar temp = this._temp_texture;\n\t\t\tif ( !temp || temp.type != tex.type || temp.width != samples ) {\n\t\t\t\tvar options = {\n\t\t\t\t\ttype: tex.type,\n\t\t\t\t\tformat: gl.RGBA,\n\t\t\t\t\tfilter: gl.NEAREST\n\t\t\t\t};\n\t\t\t\tthis._temp_texture = new GL.Texture( samples, 1, options );\n\t\t\t\tthis._temp_texture2 = new GL.Texture( samples, 1, options );\n\t\t\t\tthis._temp_texture_out = new GL.Texture( 1, 1, options );\n\t\t\t}\n\n\t\t\tvar tempA = this._temp_texture;\n\t\t\tvar tempB = this._temp_texture2;\n\n\t\t\tvar shader_copy = LGraphTextureLinearAvgSmooth._shader_copy;\n\t\t\tvar shader_avg = LGraphTextureLinearAvgSmooth._shader_avg;\n\t\t\tvar uniforms = this._uniforms;\n\t\t\tuniforms.u_samples = samples;\n\t\t\tuniforms.u_isamples = 1.0 / samples;\n\n\t\t\tgl.disable(gl.BLEND);\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\ttempA.drawTo(function() {\n\t\t\t\ttempB.bind(1);\n\t\t\t\ttex.toViewport( shader_copy, uniforms );\n\t\t\t});\n\n\t\t\tthis._temp_texture_out.drawTo(function() {\n\t\t\t\ttempA.toViewport( shader_avg, uniforms );\n\t\t\t});\n\n\t\t\tthis.setOutputData( 0, this._temp_texture_out );\n\n\t\t\t//swap\n\t\t\tthis._temp_texture = tempB;\n\t\t\tthis._temp_texture2 = tempA;\n\t\t}\n\t\telse\n\t\t\tthis.setOutputData(0, this._temp_texture_out);\n\t\tthis.setOutputData(1, this._temp_texture2);\n\t\tthis.frame++;\n\t};\n\n\tLGraphTextureLinearAvgSmooth.pixel_shader_copy =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tuniform float u_isamples;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tif( v_coord.x <= u_isamples )\\n\\\n\t\t\t\tgl_FragColor = texture2D( u_texture, vec2(0.5) );\\n\\\n\t\t\telse\\n\\\n\t\t\t\tgl_FragColor = texture2D( u_textureB, v_coord - vec2(u_isamples,0.0) );\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLGraphTextureLinearAvgSmooth.pixel_shader_avg =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform int u_samples;\\n\\\n\t\tuniform float u_isamples;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = vec4(0.0);\\n\\\n\t\t\tfor(int i = 0; i < 64; ++i)\\n\\\n\t\t\t{\\n\\\n\t\t\t\tcolor += texture2D( u_texture, vec2( float(i)*u_isamples,0.0) );\\n\\\n\t\t\t\tif(i == (u_samples - 1))\\n\\\n\t\t\t\t\tbreak;\\n\\\n\t\t\t}\\n\\\n\t\t\tgl_FragColor = color * u_isamples;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\n\tLiteGraph.registerNodeType( \"texture/linear_avg_smooth\", LGraphTextureLinearAvgSmooth );\n\n\t// Image To Texture *****************************************\n\tfunction LGraphImageToTexture() {\n\t\tthis.addInput(\"Image\", \"image\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = {};\n\t}\n\n\tLGraphImageToTexture.title = \"Image to Texture\";\n\tLGraphImageToTexture.desc = \"Uploads an image to the GPU\";\n\t//LGraphImageToTexture.widgets_info = { size: { widget:\"combo\", values:[0,32,64,128,256,512,1024,2048]} };\n\n\tLGraphImageToTexture.prototype.onExecute = function() {\n\t\tvar img = this.getInputData(0);\n\t\tif (!img) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = img.videoWidth || img.width;\n\t\tvar height = img.videoHeight || img.height;\n\n\t\t//this is in case we are using a webgl canvas already, no need to reupload it\n\t\tif (img.gltexture) {\n\t\t\tthis.setOutputData(0, img.gltexture);\n\t\t\treturn;\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\t\tif (!temp || temp.width != width || temp.height != height) {\n\t\t\tthis._temp_texture = new GL.Texture(width, height, {\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\ttry {\n\t\t\tthis._temp_texture.uploadImage(img);\n\t\t} catch (err) {\n\t\t\tconsole.error(\n\t\t\t\t\"image comes from an unsafe location, cannot be uploaded to webgl: \" +\n\t\t\t\t\terr\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/imageToTexture\",\n\t\tLGraphImageToTexture\n\t);\n\n\t// Texture LUT *****************************************\n\tfunction LGraphTextureLUT() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addInput(\"LUT\", \"Texture\");\n\t\tthis.addInput(\"Intensity\", \"number\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = { enabled: true, intensity: 1, precision: LGraphTexture.DEFAULT, texture: null };\n\n\t\tif (!LGraphTextureLUT._shader) {\n\t\t\tLGraphTextureLUT._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureLUT.pixel_shader );\n\t\t}\n\t}\n\n\tLGraphTextureLUT.widgets_info = {\n\t\ttexture: { widget: \"texture\" },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureLUT.title = \"LUT\";\n\tLGraphTextureLUT.desc = \"Apply LUT to Texture\";\n\n\tLGraphTextureLUT.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar lut_tex = this.getInputData(1);\n\n\t\tif (!lut_tex) {\n\t\t\tlut_tex = LGraphTexture.getTexture(this.properties.texture);\n\t\t}\n\n\t\tif (!lut_tex) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tlut_tex.bind(0);\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n\t\tgl.texParameteri(\n\t\t\tgl.TEXTURE_2D,\n\t\t\tgl.TEXTURE_WRAP_S,\n\t\t\tgl.CLAMP_TO_EDGE\n\t\t);\n\t\tgl.texParameteri(\n\t\t\tgl.TEXTURE_2D,\n\t\t\tgl.TEXTURE_WRAP_T,\n\t\t\tgl.CLAMP_TO_EDGE\n\t\t);\n\t\tgl.bindTexture(gl.TEXTURE_2D, null);\n\n\t\tvar intensity = this.properties.intensity;\n\t\tif (this.isInputConnected(2)) {\n\t\t\tthis.properties.intensity = intensity = this.getInputData(2);\n\t\t}\n\n\t\tthis._tex = LGraphTexture.getTargetTexture(\n\t\t\ttex,\n\t\t\tthis._tex,\n\t\t\tthis.properties.precision\n\t\t);\n\n\t\t//var mesh = Mesh.getScreenQuad();\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tlut_tex.bind(1);\n\t\t\ttex.toViewport(LGraphTextureLUT._shader, {\n\t\t\t\tu_texture: 0,\n\t\t\t\tu_textureB: 1,\n\t\t\t\tu_amount: intensity\n\t\t\t});\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureLUT.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tuniform float u_amount;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\t lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\\n\\\n\t\t\t mediump float blueColor = textureColor.b * 63.0;\\n\\\n\t\t\t mediump vec2 quad1;\\n\\\n\t\t\t quad1.y = floor(floor(blueColor) / 8.0);\\n\\\n\t\t\t quad1.x = floor(blueColor) - (quad1.y * 8.0);\\n\\\n\t\t\t mediump vec2 quad2;\\n\\\n\t\t\t quad2.y = floor(ceil(blueColor) / 8.0);\\n\\\n\t\t\t quad2.x = ceil(blueColor) - (quad2.y * 8.0);\\n\\\n\t\t\t highp vec2 texPos1;\\n\\\n\t\t\t texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\\n\\\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\\\n\t\t\t highp vec2 texPos2;\\n\\\n\t\t\t texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\\n\\\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\\\n\t\t\t lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\\n\\\n\t\t\t lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\\n\\\n\t\t\t lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\\n\\\n\t\t\t gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/LUT\", LGraphTextureLUT);\n\n\n\t// Texture LUT *****************************************\n\tfunction LGraphTextureEncode() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addInput(\"Atlas\", \"Texture\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, generate_mipmaps: false, texture: null };\n\n\t\tif (!LGraphTextureEncode._shader) {\n\t\t\tLGraphTextureEncode._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEncode.pixel_shader );\n\t\t}\n\n\t\tthis._uniforms = {\n\t\t\t\tu_texture: 0,\n\t\t\t\tu_textureB: 1,\n\t\t\t\tu_row_simbols: 4,\n\t\t\t\tu_simbol_size: 16,\n\t\t\t\tu_res: vec2.create()\n\t\t};\n\t}\n\n\tLGraphTextureEncode.widgets_info = {\n\t\ttexture: { widget: \"texture\" },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureEncode.title = \"Encode\";\n\tLGraphTextureEncode.desc = \"Apply a texture atlas to encode a texture\";\n\n\tLGraphTextureEncode.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar symbols_tex = this.getInputData(1);\n\n\t\tif (!symbols_tex) {\n\t\t\tsymbols_tex = LGraphTexture.getTexture(this.properties.texture);\n\t\t}\n\n\t\tif (!symbols_tex) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tsymbols_tex.bind(0);\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST );\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST );\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );\n\t\tgl.bindTexture(gl.TEXTURE_2D, null);\n\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_row_simbols = Math.floor(this.properties.num_row_symbols);\n\t\tuniforms.u_symbol_size = this.properties.symbol_size;\n\t\tuniforms.u_brightness = this.properties.brightness;\n\t\tuniforms.u_invert = this.properties.invert ? 1 : 0;\n\t\tuniforms.u_colorize = this.properties.colorize ? 1 : 0;\n\n\t\tthis._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );\n\t\tuniforms.u_res[0] = this._tex.width;\n\t\tuniforms.u_res[1] = this._tex.height;\n\t\tthis._tex.bind(0);\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tsymbols_tex.bind(1);\n\t\t\ttex.toViewport(LGraphTextureEncode._shader, uniforms);\n\t\t});\n\n\t\tif (this.properties.generate_mipmaps) {\n\t\t\tthis._tex.bind(0);\n\t\t\tgl.generateMipmap(this._tex.texture_type);\n\t\t\tthis._tex.unbind(0);\n\t\t}\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureEncode.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tuniform float u_row_simbols;\\n\\\n\t\tuniform float u_symbol_size;\\n\\\n\t\tuniform float u_brightness;\\n\\\n\t\tuniform float u_invert;\\n\\\n\t\tuniform float u_colorize;\\n\\\n\t\tuniform vec2 u_res;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec2 total_symbols = u_res / u_symbol_size;\\n\\\n\t\t\tvec2 uv = floor(v_coord * total_symbols) / total_symbols; //pixelate \\n\\\n\t\t\tvec2 local_uv = mod(v_coord * u_res, u_symbol_size) / u_symbol_size;\\n\\\n\t\t\tlowp vec4 textureColor = texture2D(u_texture, uv );\\n\\\n\t\t\tfloat lum = clamp(u_brightness * (textureColor.x + textureColor.y + textureColor.z)/3.0,0.0,1.0);\\n\\\n\t\t\tif( u_invert == 1.0 ) lum = 1.0 - lum;\\n\\\n\t\t\tfloat index = floor( lum * (u_row_simbols * u_row_simbols - 1.0));\\n\\\n\t\t\tfloat col = mod( index, u_row_simbols );\\n\\\n\t\t\tfloat row = u_row_simbols - floor( index / u_row_simbols ) - 1.0;\\n\\\n\t\t\tvec2 simbol_uv = ( vec2( col, row ) + local_uv ) / u_row_simbols;\\n\\\n\t\t\tvec4 color = texture2D( u_textureB, simbol_uv );\\n\\\n\t\t\tif(u_colorize == 1.0)\\n\\\n\t\t\t\tcolor *= textureColor;\\n\\\n\t\t\tgl_FragColor = color;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/encode\", LGraphTextureEncode);\n\n\t// Texture Channels *****************************************\n\tfunction LGraphTextureChannels() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\n\t\tthis.addOutput(\"R\", \"Texture\");\n\t\tthis.addOutput(\"G\", \"Texture\");\n\t\tthis.addOutput(\"B\", \"Texture\");\n\t\tthis.addOutput(\"A\", \"Texture\");\n\n\t\t//this.properties = { use_single_channel: true };\n\t\tif (!LGraphTextureChannels._shader) {\n\t\t\tLGraphTextureChannels._shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureChannels.pixel_shader\n\t\t\t);\n\t\t}\n\t}\n\n\tLGraphTextureChannels.title = \"Texture to Channels\";\n\tLGraphTextureChannels.desc = \"Split texture channels\";\n\n\tLGraphTextureChannels.prototype.onExecute = function() {\n\t\tvar texA = this.getInputData(0);\n\t\tif (!texA) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this._channels) {\n\t\t\tthis._channels = Array(4);\n\t\t}\n\n\t\t//var format = this.properties.use_single_channel ? gl.LUMINANCE : gl.RGBA; //not supported by WebGL1\n\t\tvar format = gl.RGB;\n\t\tvar connections = 0;\n\t\tfor (var i = 0; i < 4; i++) {\n\t\t\tif (this.isOutputConnected(i)) {\n\t\t\t\tif (\n\t\t\t\t\t!this._channels[i] ||\n\t\t\t\t\tthis._channels[i].width != texA.width ||\n\t\t\t\t\tthis._channels[i].height != texA.height ||\n\t\t\t\t\tthis._channels[i].type != texA.type ||\n\t\t\t\t\tthis._channels[i].format != format\n\t\t\t\t) {\n\t\t\t\t\tthis._channels[i] = new GL.Texture(\n\t\t\t\t\t\ttexA.width,\n\t\t\t\t\t\ttexA.height,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: texA.type,\n\t\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\t\tfilter: gl.LINEAR\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconnections++;\n\t\t\t} else {\n\t\t\t\tthis._channels[i] = null;\n\t\t\t}\n\t\t}\n\n\t\tif (!connections) {\n\t\t\treturn;\n\t\t}\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tvar shader = LGraphTextureChannels._shader;\n\t\tvar masks = [\n\t\t\t[1, 0, 0, 0],\n\t\t\t[0, 1, 0, 0],\n\t\t\t[0, 0, 1, 0],\n\t\t\t[0, 0, 0, 1]\n\t\t];\n\n\t\tfor (var i = 0; i < 4; i++) {\n\t\t\tif (!this._channels[i]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis._channels[i].drawTo(function() {\n\t\t\t\ttexA.bind(0);\n\t\t\t\tshader\n\t\t\t\t\t.uniforms({ u_texture: 0, u_mask: masks[i] })\n\t\t\t\t\t.draw(mesh);\n\t\t\t});\n\t\t\tthis.setOutputData(i, this._channels[i]);\n\t\t}\n\t};\n\n\tLGraphTextureChannels.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec4 u_mask;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t   gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/textureChannels\",\n\t\tLGraphTextureChannels\n\t);\n\n\t// Texture Channels to Texture *****************************************\n\tfunction LGraphChannelsTexture() {\n\t\tthis.addInput(\"R\", \"Texture\");\n\t\tthis.addInput(\"G\", \"Texture\");\n\t\tthis.addInput(\"B\", \"Texture\");\n\t\tthis.addInput(\"A\", \"Texture\");\n\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\n\t\tthis.properties = {\n\t\t\tprecision: LGraphTexture.DEFAULT,\n\t\t\tR: 1,\n\t\t\tG: 1,\n\t\t\tB: 1,\n\t\t\tA: 1\n\t\t};\n\t\tthis._color = vec4.create();\n\t\tthis._uniforms = {\n\t\t\tu_textureR: 0,\n\t\t\tu_textureG: 1,\n\t\t\tu_textureB: 2,\n\t\t\tu_textureA: 3,\n\t\t\tu_color: this._color\n\t\t};\n\t}\n\n\tLGraphChannelsTexture.title = \"Channels to Texture\";\n\tLGraphChannelsTexture.desc = \"Split texture channels\";\n\tLGraphChannelsTexture.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphChannelsTexture.prototype.onExecute = function() {\n\t\tvar white = LGraphTexture.getWhiteTexture();\n\t\tvar texR = this.getInputData(0) || white;\n\t\tvar texG = this.getInputData(1) || white;\n\t\tvar texB = this.getInputData(2) || white;\n\t\tvar texA = this.getInputData(3) || white;\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tif (!LGraphChannelsTexture._shader) {\n\t\t\tLGraphChannelsTexture._shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphChannelsTexture.pixel_shader\n\t\t\t);\n\t\t}\n\t\tvar shader = LGraphChannelsTexture._shader;\n\n\t\tvar w = Math.max(texR.width, texG.width, texB.width, texA.width);\n\t\tvar h = Math.max(\n\t\t\ttexR.height,\n\t\t\ttexG.height,\n\t\t\ttexB.height,\n\t\t\ttexA.height\n\t\t);\n\t\tvar type =\n\t\t\tthis.properties.precision == LGraphTexture.HIGH\n\t\t\t\t? LGraphTexture.HIGH_PRECISION_FORMAT\n\t\t\t\t: gl.UNSIGNED_BYTE;\n\n\t\tif (\n\t\t\t!this._texture ||\n\t\t\tthis._texture.width != w ||\n\t\t\tthis._texture.height != h ||\n\t\t\tthis._texture.type != type\n\t\t) {\n\t\t\tthis._texture = new GL.Texture(w, h, {\n\t\t\t\ttype: type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tvar color = this._color;\n\t\tcolor[0] = this.properties.R;\n\t\tcolor[1] = this.properties.G;\n\t\tcolor[2] = this.properties.B;\n\t\tcolor[3] = this.properties.A;\n\t\tvar uniforms = this._uniforms;\n\n\t\tthis._texture.drawTo(function() {\n\t\t\ttexR.bind(0);\n\t\t\ttexG.bind(1);\n\t\t\ttexB.bind(2);\n\t\t\ttexA.bind(3);\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\t\tthis.setOutputData(0, this._texture);\n\t};\n\n\tLGraphChannelsTexture.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_textureR;\\n\\\n\t\tuniform sampler2D u_textureG;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tuniform sampler2D u_textureA;\\n\\\n\t\tuniform vec4 u_color;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t   gl_FragColor = u_color * vec4( \\\n\t\t\t\t\ttexture2D(u_textureR, v_coord).r,\\\n\t\t\t\t\ttexture2D(u_textureG, v_coord).r,\\\n\t\t\t\t\ttexture2D(u_textureB, v_coord).r,\\\n\t\t\t\t\ttexture2D(u_textureA, v_coord).r);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/channelsTexture\",\n\t\tLGraphChannelsTexture\n\t);\n\n\t// Texture Color *****************************************\n\tfunction LGraphTextureColor() {\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\n\t\tthis._tex_color = vec4.create();\n\t\tthis.properties = {\n\t\t\tcolor: vec4.create(),\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureColor.title = \"Color\";\n\tLGraphTextureColor.desc =\n\t\t\"Generates a 1x1 texture with a constant color\";\n\n\tLGraphTextureColor.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureColor.prototype.onDrawBackground = function(ctx) {\n\t\tvar c = this.properties.color;\n\t\tctx.fillStyle =\n\t\t\t\"rgb(\" +\n\t\t\tMath.floor(clamp(c[0], 0, 1) * 255) +\n\t\t\t\",\" +\n\t\t\tMath.floor(clamp(c[1], 0, 1) * 255) +\n\t\t\t\",\" +\n\t\t\tMath.floor(clamp(c[2], 0, 1) * 255) +\n\t\t\t\")\";\n\t\tif (this.flags.collapsed) {\n\t\t\tthis.boxcolor = ctx.fillStyle;\n\t\t} else {\n\t\t\tctx.fillRect(0, 0, this.size[0], this.size[1]);\n\t\t}\n\t};\n\n\tLGraphTextureColor.prototype.onExecute = function() {\n\t\tvar type =\n\t\t\tthis.properties.precision == LGraphTexture.HIGH\n\t\t\t\t? LGraphTexture.HIGH_PRECISION_FORMAT\n\t\t\t\t: gl.UNSIGNED_BYTE;\n\n\t\tif (!this._tex || this._tex.type != type) {\n\t\t\tthis._tex = new GL.Texture(1, 1, {\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\ttype: type,\n\t\t\t\tminFilter: gl.NEAREST\n\t\t\t});\n\t\t}\n\t\tvar color = this.properties.color;\n\n\t\tif (this.inputs) {\n\t\t\tfor (var i = 0; i < this.inputs.length; i++) {\n\t\t\t\tvar input = this.inputs[i];\n\t\t\t\tvar v = this.getInputData(i);\n\t\t\t\tif (v === undefined) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tswitch (input.name) {\n\t\t\t\t\tcase \"RGB\":\n\t\t\t\t\tcase \"RGBA\":\n\t\t\t\t\t\tcolor.set(v);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"R\":\n\t\t\t\t\t\tcolor[0] = v;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"G\":\n\t\t\t\t\t\tcolor[1] = v;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"B\":\n\t\t\t\t\t\tcolor[2] = v;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"A\":\n\t\t\t\t\t\tcolor[3] = v;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (vec4.sqrDist(this._tex_color, color) > 0.001) {\n\t\t\tthis._tex_color.set(color);\n\t\t\tthis._tex.fill(color);\n\t\t}\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureColor.prototype.onGetInputs = function() {\n\t\treturn [\n\t\t\t[\"RGB\", \"vec3\"],\n\t\t\t[\"RGBA\", \"vec4\"],\n\t\t\t[\"R\", \"number\"],\n\t\t\t[\"G\", \"number\"],\n\t\t\t[\"B\", \"number\"],\n\t\t\t[\"A\", \"number\"]\n\t\t];\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/color\", LGraphTextureColor);\n\n\t// Texture Channels to Texture *****************************************\n\tfunction LGraphTextureGradient() {\n\t\tthis.addInput(\"A\", \"color\");\n\t\tthis.addInput(\"B\", \"color\");\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\n\t\tthis.properties = {\n\t\t\tangle: 0,\n\t\t\tscale: 1,\n\t\t\tA: [0, 0, 0],\n\t\t\tB: [1, 1, 1],\n\t\t\ttexture_size: 32\n\t\t};\n\t\tif (!LGraphTextureGradient._shader) {\n\t\t\tLGraphTextureGradient._shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureGradient.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tthis._uniforms = {\n\t\t\tu_angle: 0,\n\t\t\tu_colorA: vec3.create(),\n\t\t\tu_colorB: vec3.create()\n\t\t};\n\t}\n\n\tLGraphTextureGradient.title = \"Gradient\";\n\tLGraphTextureGradient.desc = \"Generates a gradient\";\n\tLGraphTextureGradient[\"@A\"] = { type: \"color\" };\n\tLGraphTextureGradient[\"@B\"] = { type: \"color\" };\n\tLGraphTextureGradient[\"@texture_size\"] = {\n\t\ttype: \"enum\",\n\t\tvalues: [32, 64, 128, 256, 512]\n\t};\n\n\tLGraphTextureGradient.prototype.onExecute = function() {\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tvar mesh = GL.Mesh.getScreenQuad();\n\t\tvar shader = LGraphTextureGradient._shader;\n\n\t\tvar A = this.getInputData(0);\n\t\tif (!A) {\n\t\t\tA = this.properties.A;\n\t\t}\n\t\tvar B = this.getInputData(1);\n\t\tif (!B) {\n\t\t\tB = this.properties.B;\n\t\t}\n\n\t\t//angle and scale\n\t\tfor (var i = 2; i < this.inputs.length; i++) {\n\t\t\tvar input = this.inputs[i];\n\t\t\tvar v = this.getInputData(i);\n\t\t\tif (v === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.properties[input.name] = v;\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\t\tthis._uniforms.u_angle = this.properties.angle * DEG2RAD;\n\t\tthis._uniforms.u_scale = this.properties.scale;\n\t\tvec3.copy(uniforms.u_colorA, A);\n\t\tvec3.copy(uniforms.u_colorB, B);\n\n\t\tvar size = parseInt(this.properties.texture_size);\n\t\tif (!this._tex || this._tex.width != size) {\n\t\t\tthis._tex = new GL.Texture(size, size, {\n\t\t\t\tformat: gl.RGB,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureGradient.prototype.onGetInputs = function() {\n\t\treturn [[\"angle\", \"number\"], [\"scale\", \"number\"]];\n\t};\n\n\tLGraphTextureGradient.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform float u_angle;\\n\\\n\t\tuniform float u_scale;\\n\\\n\t\tuniform vec3 u_colorA;\\n\\\n\t\tuniform vec3 u_colorB;\\n\\\n\t\t\\n\\\n\t\tvec2 rotate(vec2 v, float angle)\\n\\\n\t\t{\\n\\\n\t\t\tvec2 result;\\n\\\n\t\t\tfloat _cos = cos(angle);\\n\\\n\t\t\tfloat _sin = sin(angle);\\n\\\n\t\t\tresult.x = v.x * _cos - v.y * _sin;\\n\\\n\t\t\tresult.y = v.x * _sin + v.y * _cos;\\n\\\n\t\t\treturn result;\\n\\\n\t\t}\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tfloat f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\\n\\\n\t\t\tvec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\\n\\\n\t\t   gl_FragColor = vec4(color,1.0);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/gradient\", LGraphTextureGradient);\n\n\t// Texture Mix *****************************************\n\tfunction LGraphTextureMix() {\n\t\tthis.addInput(\"A\", \"Texture\");\n\t\tthis.addInput(\"B\", \"Texture\");\n\t\tthis.addInput(\"Mixer\", \"Texture\");\n\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\t\tthis.properties = { factor: 0.5, size_from_biggest: true, invert: false, precision: LGraphTexture.DEFAULT };\n\t\tthis._uniforms = {\n\t\t\tu_textureA: 0,\n\t\t\tu_textureB: 1,\n\t\t\tu_textureMix: 2,\n\t\t\tu_mix: vec4.create()\n\t\t};\n\t}\n\n\tLGraphTextureMix.title = \"Mix\";\n\tLGraphTextureMix.desc = \"Generates a texture mixing two textures\";\n\n\tLGraphTextureMix.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureMix.prototype.onExecute = function() {\n\t\tvar texA = this.getInputData(0);\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, texA);\n\t\t\treturn;\n\t\t}\n\n\t\tvar texB = this.getInputData(1);\n\t\tif (!texA || !texB) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar texMix = this.getInputData(2);\n\n\t\tvar factor = this.getInputData(3);\n\n\t\tthis._tex = LGraphTexture.getTargetTexture(\n\t\t\tthis.properties.size_from_biggest && texB.width > texA.width ? texB : texA,\n\t\t\tthis._tex,\n\t\t\tthis.properties.precision\n\t\t);\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tvar shader = null;\n\t\tvar uniforms = this._uniforms;\n\t\tif (texMix) {\n\t\t\tshader = LGraphTextureMix._shader_tex;\n\t\t\tif (!shader) {\n\t\t\t\tshader = LGraphTextureMix._shader_tex = new GL.Shader(\n\t\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\tLGraphTextureMix.pixel_shader,\n\t\t\t\t\t{ MIX_TEX: \"\" }\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tshader = LGraphTextureMix._shader_factor;\n\t\t\tif (!shader) {\n\t\t\t\tshader = LGraphTextureMix._shader_factor = new GL.Shader(\n\t\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\tLGraphTextureMix.pixel_shader\n\t\t\t\t);\n\t\t\t}\n\t\t\tvar f = factor == null ? this.properties.factor : factor;\n\t\t\tuniforms.u_mix.set([f, f, f, f]);\n\t\t}\n\n\t\tvar invert = this.properties.invert;\n\n\t\tthis._tex.drawTo(function() {\n\t\t\ttexA.bind( invert ? 1 : 0 );\n\t\t\ttexB.bind( invert ? 0 : 1 );\n\t\t\tif (texMix) {\n\t\t\t\ttexMix.bind(2);\n\t\t\t}\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureMix.prototype.onGetInputs = function() {\n\t\treturn [[\"factor\", \"number\"]];\n\t};\n\n\tLGraphTextureMix.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_textureA;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\t#ifdef MIX_TEX\\n\\\n\t\t\tuniform sampler2D u_textureMix;\\n\\\n\t\t#else\\n\\\n\t\t\tuniform vec4 u_mix;\\n\\\n\t\t#endif\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\t#ifdef MIX_TEX\\n\\\n\t\t\t   vec4 f = texture2D(u_textureMix, v_coord);\\n\\\n\t\t\t#else\\n\\\n\t\t\t   vec4 f = u_mix;\\n\\\n\t\t\t#endif\\n\\\n\t\t   gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), f );\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/mix\", LGraphTextureMix);\n\n\t// Texture Edges detection *****************************************\n\tfunction LGraphTextureEdges() {\n\t\tthis.addInput(\"Tex.\", \"Texture\");\n\n\t\tthis.addOutput(\"Edges\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tinvert: true,\n\t\t\tthreshold: false,\n\t\t\tfactor: 1,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\n\t\tif (!LGraphTextureEdges._shader) {\n\t\t\tLGraphTextureEdges._shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureEdges.pixel_shader\n\t\t\t);\n\t\t}\n\t}\n\n\tLGraphTextureEdges.title = \"Edges\";\n\tLGraphTextureEdges.desc = \"Detects edges\";\n\n\tLGraphTextureEdges.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureEdges.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._tex = LGraphTexture.getTargetTexture(\n\t\t\ttex,\n\t\t\tthis._tex,\n\t\t\tthis.properties.precision\n\t\t);\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tvar shader = LGraphTextureEdges._shader;\n\t\tvar invert = this.properties.invert;\n\t\tvar factor = this.properties.factor;\n\t\tvar threshold = this.properties.threshold ? 1 : 0;\n\n\t\tthis._tex.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader\n\t\t\t\t.uniforms({\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tu_isize: [1 / tex.width, 1 / tex.height],\n\t\t\t\t\tu_factor: factor,\n\t\t\t\t\tu_threshold: threshold,\n\t\t\t\t\tu_invert: invert ? 1 : 0\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureEdges.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 u_isize;\\n\\\n\t\tuniform int u_invert;\\n\\\n\t\tuniform float u_factor;\\n\\\n\t\tuniform float u_threshold;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 center = texture2D(u_texture, v_coord);\\n\\\n\t\t\tvec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\\n\\\n\t\t\tvec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\\n\\\n\t\t\tvec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\\n\\\n\t\t\tvec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\\n\\\n\t\t\tvec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\\n\\\n\t\t\tdiff *= u_factor;\\n\\\n\t\t\tif(u_invert == 1)\\n\\\n\t\t\t\tdiff.xyz = vec3(1.0) - diff.xyz;\\n\\\n\t\t\tif( u_threshold == 0.0 )\\n\\\n\t\t\t\tgl_FragColor = vec4( diff.xyz, center.a );\\n\\\n\t\t\telse\\n\\\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\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/edges\", LGraphTextureEdges);\n\n\t// Texture Depth *****************************************\n\tfunction LGraphTextureDepthRange() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addInput(\"Distance\", \"number\");\n\t\tthis.addInput(\"Range\", \"number\");\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tdistance: 100,\n\t\t\trange: 50,\n\t\t\tonly_depth: false,\n\t\t\thigh_precision: false\n\t\t};\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_distance: 100,\n\t\t\tu_range: 50,\n\t\t\tu_camera_planes: null\n\t\t};\n\t}\n\n\tLGraphTextureDepthRange.title = \"Depth Range\";\n\tLGraphTextureDepthRange.desc = \"Generates a texture with a depth range\";\n\n\tLGraphTextureDepthRange.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar precision = gl.UNSIGNED_BYTE;\n\t\tif (this.properties.high_precision) {\n\t\t\tprecision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT;\n\t\t}\n\n\t\tif (\n\t\t\t!this._temp_texture ||\n\t\t\tthis._temp_texture.type != precision ||\n\t\t\tthis._temp_texture.width != tex.width ||\n\t\t\tthis._temp_texture.height != tex.height\n\t\t) {\n\t\t\tthis._temp_texture = new GL.Texture(tex.width, tex.height, {\n\t\t\t\ttype: precision,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\n\t\t//iterations\n\t\tvar distance = this.properties.distance;\n\t\tif (this.isInputConnected(1)) {\n\t\t\tdistance = this.getInputData(1);\n\t\t\tthis.properties.distance = distance;\n\t\t}\n\n\t\tvar range = this.properties.range;\n\t\tif (this.isInputConnected(2)) {\n\t\t\trange = this.getInputData(2);\n\t\t\tthis.properties.range = range;\n\t\t}\n\n\t\tuniforms.u_distance = distance;\n\t\tuniforms.u_range = range;\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tif (!LGraphTextureDepthRange._shader) {\n\t\t\tLGraphTextureDepthRange._shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureDepthRange.pixel_shader\n\t\t\t);\n\t\t\tLGraphTextureDepthRange._shader_onlydepth = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureDepthRange.pixel_shader,\n\t\t\t\t{ ONLY_DEPTH: \"\" }\n\t\t\t);\n\t\t}\n\t\tvar shader = this.properties.only_depth\n\t\t\t? LGraphTextureDepthRange._shader_onlydepth\n\t\t\t: LGraphTextureDepthRange._shader;\n\n\t\t//NEAR AND FAR PLANES\n\t\tvar planes = null;\n\t\tif (tex.near_far_planes) {\n\t\t\tplanes = tex.near_far_planes;\n\t\t} else if (window.LS && LS.Renderer._main_camera) {\n\t\t\tplanes = LS.Renderer._main_camera._uniforms.u_camera_planes;\n\t\t} else {\n\t\t\tplanes = [0.1, 1000];\n\t\t} //hardcoded\n\t\tuniforms.u_camera_planes = planes;\n\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\n\t\tthis._temp_texture.near_far_planes = planes;\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\tLGraphTextureDepthRange.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 u_camera_planes;\\n\\\n\t\tuniform float u_distance;\\n\\\n\t\tuniform float u_range;\\n\\\n\t\t\\n\\\n\t\tfloat LinearDepth()\\n\\\n\t\t{\\n\\\n\t\t\tfloat zNear = u_camera_planes.x;\\n\\\n\t\t\tfloat zFar = u_camera_planes.y;\\n\\\n\t\t\tfloat depth = texture2D(u_texture, v_coord).x;\\n\\\n\t\t\tdepth = depth * 2.0 - 1.0;\\n\\\n\t\t\treturn zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tfloat depth = LinearDepth();\\n\\\n\t\t\t#ifdef ONLY_DEPTH\\n\\\n\t\t\t   gl_FragColor = vec4(depth);\\n\\\n\t\t\t#else\\n\\\n\t\t\t\tfloat diff = abs(depth * u_camera_planes.y - u_distance);\\n\\\n\t\t\t\tfloat dof = 1.0;\\n\\\n\t\t\t\tif(diff <= u_range)\\n\\\n\t\t\t\t\tdof = diff / u_range;\\n\\\n\t\t\t   gl_FragColor = vec4(dof);\\n\\\n\t\t\t#endif\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType( \"texture/depth_range\", LGraphTextureDepthRange );\n\n\n\t// Texture Depth *****************************************\n\tfunction LGraphTextureLinearDepth() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tprecision: LGraphTexture.DEFAULT,\n\t\t\tinvert: false\n\t\t};\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_camera_planes: null, //filled later\n\t\t\tu_ires: vec2.create()\n\t\t};\n\t}\n\n\tLGraphTextureLinearDepth.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureLinearDepth.title = \"Linear Depth\";\n\tLGraphTextureLinearDepth.desc = \"Creates a color texture with linear depth\";\n\n\tLGraphTextureLinearDepth.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex || (tex.format != gl.DEPTH_COMPONENT && tex.format != gl.DEPTH_STENCIL) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar precision = this.properties.precision == LGraphTexture.HIGH ? gl.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE;\n\n\t\tif ( !this._temp_texture || this._temp_texture.type != precision || this._temp_texture.width != tex.width || this._temp_texture.height != tex.height ) {\n\t\t\tthis._temp_texture = new GL.Texture(tex.width, tex.height, {\n\t\t\t\ttype: precision,\n\t\t\t\tformat: gl.RGB,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_invert = this.properties.invert ? 1 : 0;\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tif(!LGraphTextureLinearDepth._shader)\n\t\t\tLGraphTextureLinearDepth._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearDepth.pixel_shader);\n\t\tvar shader = LGraphTextureLinearDepth._shader;\n\n\t\t//NEAR AND FAR PLANES\n\t\tvar planes = null;\n\t\tif (tex.near_far_planes) {\n\t\t\tplanes = tex.near_far_planes;\n\t\t} else if (window.LS && LS.Renderer._main_camera) {\n\t\t\tplanes = LS.Renderer._main_camera._uniforms.u_camera_planes;\n\t\t} else {\n\t\t\tplanes = [0.1, 1000];\n\t\t} //hardcoded\n\t\tuniforms.u_camera_planes = planes;\n\t\t//uniforms.u_ires.set([1/tex.width, 1/tex.height]);\n\t\tuniforms.u_ires.set([0,0]);\n\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\n\t\tthis._temp_texture.near_far_planes = planes;\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\tLGraphTextureLinearDepth.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 u_camera_planes;\\n\\\n\t\tuniform int u_invert;\\n\\\n\t\tuniform vec2 u_ires;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tfloat zNear = u_camera_planes.x;\\n\\\n\t\t\tfloat zFar = u_camera_planes.y;\\n\\\n\t\t\tfloat depth = texture2D(u_texture, v_coord + u_ires*0.5).x * 2.0 - 1.0;\\n\\\n\t\t\tfloat f = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\\n\\\n\t\t\tif( u_invert == 1 )\\n\\\n\t\t\t\tf = 1.0 - f;\\n\\\n\t\t\tgl_FragColor = vec4(vec3(f),1.0);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType( \"texture/linear_depth\", LGraphTextureLinearDepth );\n\n\t// Texture Blur *****************************************\n\tfunction LGraphTextureBlur() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addInput(\"Iterations\", \"number\");\n\t\tthis.addInput(\"Intensity\", \"number\");\n\t\tthis.addOutput(\"Blurred\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tintensity: 1,\n\t\t\titerations: 1,\n\t\t\tpreserve_aspect: false,\n\t\t\tscale: [1, 1],\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureBlur.title = \"Blur\";\n\tLGraphTextureBlur.desc = \"Blur a texture\";\n\n\tLGraphTextureBlur.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureBlur.max_iterations = 20;\n\n\tLGraphTextureBlur.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar temp = this._final_texture;\n\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\t//we need two textures to do the blurring\n\t\t\t//this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });\n\t\t\ttemp = this._final_texture = new GL.Texture(\n\t\t\t\ttex.width,\n\t\t\t\ttex.height,\n\t\t\t\t{ type: tex.type, format: gl.RGBA, filter: gl.LINEAR }\n\t\t\t);\n\t\t}\n\n\t\t//iterations\n\t\tvar iterations = this.properties.iterations;\n\t\tif (this.isInputConnected(1)) {\n\t\t\titerations = this.getInputData(1);\n\t\t\tthis.properties.iterations = iterations;\n\t\t}\n\t\titerations = Math.min(\n\t\t\tMath.floor(iterations),\n\t\t\tLGraphTextureBlur.max_iterations\n\t\t);\n\t\tif (iterations == 0) {\n\t\t\t//skip blurring\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar intensity = this.properties.intensity;\n\t\tif (this.isInputConnected(2)) {\n\t\t\tintensity = this.getInputData(2);\n\t\t\tthis.properties.intensity = intensity;\n\t\t}\n\n\t\t//blur sometimes needs an aspect correction\n\t\tvar aspect = LiteGraph.camera_aspect;\n\t\tif (!aspect && window.gl !== undefined) {\n\t\t\taspect = gl.canvas.height / gl.canvas.width;\n\t\t}\n\t\tif (!aspect) {\n\t\t\taspect = 1;\n\t\t}\n\t\taspect = this.properties.preserve_aspect ? aspect : 1;\n\n\t\tvar scale = this.properties.scale || [1, 1];\n\t\ttex.applyBlur(aspect * scale[0], scale[1], intensity, temp);\n\t\tfor (var i = 1; i < iterations; ++i) {\n\t\t\ttemp.applyBlur(\n\t\t\t\taspect * scale[0] * (i + 1),\n\t\t\t\tscale[1] * (i + 1),\n\t\t\t\tintensity\n\t\t\t);\n\t\t}\n\n\t\tthis.setOutputData(0, temp);\n\t};\n\n\t/*\nLGraphTextureBlur.pixel_shader = \"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 u_offset;\\n\\\n\t\tuniform float u_intensity;\\n\\\n\t\tvoid main() {\\n\\\n\t\t   vec4 sum = vec4(0.0);\\n\\\n\t\t   vec4 center = texture2D(u_texture, v_coord);\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * -4.0) * 0.05/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * -3.0) * 0.09/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * -2.0) * 0.12/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * -1.0) * 0.15/0.98;\\n\\\n\t\t   sum += center * 0.16/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * 4.0) * 0.05/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * 3.0) * 0.09/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * 2.0) * 0.12/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * 1.0) * 0.15/0.98;\\n\\\n\t\t   gl_FragColor = u_intensity * sum;\\n\\\n\t\t}\\n\\\n\t\t\";\n*/\n\n\tLiteGraph.registerNodeType(\"texture/blur\", LGraphTextureBlur);\n\n\t//Independent glow FX\n\t//based on https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/\n\tfunction FXGlow()\n\t{\n\t\tthis.intensity = 0.5;\n\t\tthis.persistence = 0.6;\n\t\tthis.iterations = 8;\n\t\tthis.threshold = 0.8;\n\t\tthis.scale = 1;\n\n\t\tthis.dirt_texture = null;\n\t\tthis.dirt_factor = 0.5;\n\n\t\tthis._textures = [];\n\t\tthis._uniforms = {\n\t\t\tu_intensity: 1,\n\t\t\tu_texture: 0,\n\t\t\tu_glow_texture: 1,\n\t\t\tu_threshold: 0,\n\t\t\tu_texel_size: vec2.create()\n\t\t};\n\t}\n\n\tFXGlow.prototype.applyFX = function( tex, output_texture, glow_texture, average_texture ) {\n\n\t\tvar width = tex.width;\n\t\tvar height = tex.height;\n\n\t\tvar texture_info = {\n\t\t\tformat: tex.format,\n\t\t\ttype: tex.type,\n\t\t\tminFilter: GL.LINEAR,\n\t\t\tmagFilter: GL.LINEAR,\n\t\t\twrap: gl.CLAMP_TO_EDGE\n\t\t};\n\n\t\tvar uniforms = this._uniforms;\n\t\tvar textures = this._textures;\n\n\t\t//cut\n\t\tvar shader = FXGlow._cut_shader;\n\t\tif (!shader) {\n\t\t\tshader = FXGlow._cut_shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tFXGlow.cut_pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tgl.disable(gl.BLEND);\n\n\t\tuniforms.u_threshold = this.threshold;\n\t\tvar currentDestination = (textures[0] = GL.Texture.getTemporary(\n\t\t\twidth,\n\t\t\theight,\n\t\t\ttexture_info\n\t\t));\n\t\ttex.blit( currentDestination, shader.uniforms(uniforms) );\n\t\tvar currentSource = currentDestination;\n\n\t\tvar iterations = this.iterations;\n\t\titerations = clamp(iterations, 1, 16) | 0;\n\t\tvar texel_size = uniforms.u_texel_size;\n\t\tvar intensity = this.intensity;\n\n\t\tuniforms.u_intensity = 1;\n\t\tuniforms.u_delta = this.scale; //1\n\n\t\t//downscale/upscale shader\n\t\tvar shader = FXGlow._shader;\n\t\tif (!shader) {\n\t\t\tshader = FXGlow._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tFXGlow.scale_pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar i = 1;\n\t\t//downscale\n\t\tfor (; i < iterations; i++) {\n\t\t\twidth = width >> 1;\n\t\t\tif ((height | 0) > 1) {\n\t\t\t\theight = height >> 1;\n\t\t\t}\n\t\t\tif (width < 2) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcurrentDestination = textures[i] = GL.Texture.getTemporary(\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\ttexture_info\n\t\t\t);\n\t\t\ttexel_size[0] = 1 / currentSource.width;\n\t\t\ttexel_size[1] = 1 / currentSource.height;\n\t\t\tcurrentSource.blit(\n\t\t\t\tcurrentDestination,\n\t\t\t\tshader.uniforms(uniforms)\n\t\t\t);\n\t\t\tcurrentSource = currentDestination;\n\t\t}\n\n\t\t//average\n\t\tif (average_texture) {\n\t\t\ttexel_size[0] = 1 / currentSource.width;\n\t\t\ttexel_size[1] = 1 / currentSource.height;\n\t\t\tuniforms.u_intensity = intensity;\n\t\t\tuniforms.u_delta = 1;\n\t\t\tcurrentSource.blit(average_texture, shader.uniforms(uniforms));\n\t\t}\n\n\t\t//upscale and blend\n\t\tgl.enable(gl.BLEND);\n\t\tgl.blendFunc(gl.ONE, gl.ONE);\n\t\tuniforms.u_intensity = this.persistence;\n\t\tuniforms.u_delta = 0.5;\n\n\t\t// i-=2 => -1 to point to last element in array, -1 to go to texture above\n\t\tfor ( i -= 2; i >= 0; i-- ) \n\t\t{\n\t\t\tcurrentDestination = textures[i];\n\t\t\ttextures[i] = null;\n\t\t\ttexel_size[0] = 1 / currentSource.width;\n\t\t\ttexel_size[1] = 1 / currentSource.height;\n\t\t\tcurrentSource.blit(\n\t\t\t\tcurrentDestination,\n\t\t\t\tshader.uniforms(uniforms)\n\t\t\t);\n\t\t\tGL.Texture.releaseTemporary(currentSource);\n\t\t\tcurrentSource = currentDestination;\n\t\t}\n\t\tgl.disable(gl.BLEND);\n\n\t\t//glow\n\t\tif (glow_texture) {\n\t\t\tcurrentSource.blit(glow_texture);\n\t\t}\n\n\t\t//final composition\n\t\tif ( output_texture ) {\n\t\t\tvar final_texture = output_texture;\n\t\t\tvar dirt_texture = this.dirt_texture;\n\t\t\tvar dirt_factor = this.dirt_factor;\n\t\t\tuniforms.u_intensity = intensity;\n\n\t\t\tshader = dirt_texture\n\t\t\t\t? FXGlow._dirt_final_shader\n\t\t\t\t: FXGlow._final_shader;\n\t\t\tif (!shader) {\n\t\t\t\tif (dirt_texture) {\n\t\t\t\t\tshader = FXGlow._dirt_final_shader = new GL.Shader(\n\t\t\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\t\tFXGlow.final_pixel_shader,\n\t\t\t\t\t\t{ USE_DIRT: \"\" }\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tshader = FXGlow._final_shader = new GL.Shader(\n\t\t\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\t\tFXGlow.final_pixel_shader\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfinal_texture.drawTo(function() {\n\t\t\t\ttex.bind(0);\n\t\t\t\tcurrentSource.bind(1);\n\t\t\t\tif (dirt_texture) {\n\t\t\t\t\tshader.setUniform(\"u_dirt_factor\", dirt_factor);\n\t\t\t\t\tshader.setUniform(\n\t\t\t\t\t\t\"u_dirt_texture\",\n\t\t\t\t\t\tdirt_texture.bind(2)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tshader.toViewport(uniforms);\n\t\t\t});\n\t\t}\n\n\t\tGL.Texture.releaseTemporary(currentSource);\n\t};\n\n\tFXGlow.cut_pixel_shader =\n\t\t\"precision highp float;\\n\\\n\tvarying vec2 v_coord;\\n\\\n\tuniform sampler2D u_texture;\\n\\\n\tuniform float u_threshold;\\n\\\n\tvoid main() {\\n\\\n\t\tgl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\\n\\\n\t}\";\n\n\tFXGlow.scale_pixel_shader =\n\t\t\"precision highp float;\\n\\\n\tvarying vec2 v_coord;\\n\\\n\tuniform sampler2D u_texture;\\n\\\n\tuniform vec2 u_texel_size;\\n\\\n\tuniform float u_delta;\\n\\\n\tuniform float u_intensity;\\n\\\n\t\\n\\\n\tvec4 sampleBox(vec2 uv) {\\n\\\n\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\\n\\\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\\\n\t\treturn s * 0.25;\\n\\\n\t}\\n\\\n\tvoid main() {\\n\\\n\t\tgl_FragColor = u_intensity * sampleBox( v_coord );\\n\\\n\t}\";\n\n\tFXGlow.final_pixel_shader =\n\t\t\"precision highp float;\\n\\\n\tvarying vec2 v_coord;\\n\\\n\tuniform sampler2D u_texture;\\n\\\n\tuniform sampler2D u_glow_texture;\\n\\\n\t#ifdef USE_DIRT\\n\\\n\t\tuniform sampler2D u_dirt_texture;\\n\\\n\t#endif\\n\\\n\tuniform vec2 u_texel_size;\\n\\\n\tuniform float u_delta;\\n\\\n\tuniform float u_intensity;\\n\\\n\tuniform float u_dirt_factor;\\n\\\n\t\\n\\\n\tvec4 sampleBox(vec2 uv) {\\n\\\n\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\\n\\\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\\\n\t\treturn s * 0.25;\\n\\\n\t}\\n\\\n\tvoid main() {\\n\\\n\t\tvec4 glow = sampleBox( v_coord );\\n\\\n\t\t#ifdef USE_DIRT\\n\\\n\t\t\tglow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\\n\\\n\t\t#endif\\n\\\n\t\tgl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\\n\\\n\t}\";\n\n\n\t// Texture Glow *****************************************\n\tfunction LGraphTextureGlow() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"dirt\", \"Texture\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.addOutput(\"glow\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tenabled: true,\n\t\t\tintensity: 1,\n\t\t\tpersistence: 0.99,\n\t\t\titerations: 16,\n\t\t\tthreshold: 0,\n\t\t\tscale: 1,\n\t\t\tdirt_factor: 0.5,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\n\t\tthis.fx = new FXGlow();\n\t}\n\n\tLGraphTextureGlow.title = \"Glow\";\n\tLGraphTextureGlow.desc = \"Filters a texture giving it a glow effect\";\n\n\tLGraphTextureGlow.widgets_info = {\n\t\titerations: {\n\t\t\ttype: \"number\",\n\t\t\tmin: 0,\n\t\t\tmax: 16,\n\t\t\tstep: 1,\n\t\t\tprecision: 0\n\t\t},\n\t\tthreshold: {\n\t\t\ttype: \"number\",\n\t\t\tmin: 0,\n\t\t\tmax: 10,\n\t\t\tstep: 0.01,\n\t\t\tprecision: 2\n\t\t},\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureGlow.prototype.onGetInputs = function() {\n\t\treturn [\n\t\t\t[\"enabled\", \"boolean\"],\n\t\t\t[\"threshold\", \"number\"],\n\t\t\t[\"intensity\", \"number\"],\n\t\t\t[\"persistence\", \"number\"],\n\t\t\t[\"iterations\", \"number\"],\n\t\t\t[\"dirt_factor\", \"number\"]\n\t\t];\n\t};\n\n\tLGraphTextureGlow.prototype.onGetOutputs = function() {\n\t\treturn [[\"average\", \"Texture\"]];\n\t};\n\n\tLGraphTextureGlow.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isAnyOutputConnected()) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (\n\t\t\tthis.properties.precision === LGraphTexture.PASS_THROUGH ||\n\t\t\tthis.getInputOrProperty(\"enabled\") === false\n\t\t) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = tex.width;\n\t\tvar height = tex.height;\n\n\t\tvar fx = this.fx;\n\t\tfx.threshold = this.getInputOrProperty(\"threshold\");\n\t\tfx.iterations = this.getInputOrProperty(\"iterations\");\n\t\tfx.intensity = this.getInputOrProperty(\"intensity\");\n\t\tfx.persistence = this.getInputOrProperty(\"persistence\");\n\t\tfx.dirt_texture = this.getInputData(1);\n\t\tfx.dirt_factor = this.getInputOrProperty(\"dirt_factor\");\n\t\tfx.scale = this.properties.scale;\n\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision, tex );\n\n\t\tvar average_texture = null;\n\t\tif (this.isOutputConnected(2)) {\n\t\t\taverage_texture = this._average_texture;\n\t\t\tif (\n\t\t\t\t!average_texture ||\n\t\t\t\taverage_texture.type != tex.type ||\n\t\t\t\taverage_texture.format != tex.format\n\t\t\t) {\n\t\t\t\taverage_texture = this._average_texture = new GL.Texture(\n\t\t\t\t\t1,\n\t\t\t\t\t1,\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: tex.type,\n\t\t\t\t\t\tformat: tex.format,\n\t\t\t\t\t\tfilter: gl.LINEAR\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tvar glow_texture = null;\n\t\tif (this.isOutputConnected(1)) {\n\t\t\tglow_texture = this._glow_texture;\n\t\t\tif (\n\t\t\t\t!glow_texture ||\n\t\t\t\tglow_texture.width != tex.width ||\n\t\t\t\tglow_texture.height != tex.height ||\n\t\t\t\tglow_texture.type != type ||\n\t\t\t\tglow_texture.format != tex.format\n\t\t\t) {\n\t\t\t\tglow_texture = this._glow_texture = new GL.Texture(\n\t\t\t\t\ttex.width,\n\t\t\t\t\ttex.height,\n\t\t\t\t\t{ type: type, format: tex.format, filter: gl.LINEAR }\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tvar final_texture = null;\n\t\tif (this.isOutputConnected(0)) {\n\t\t\tfinal_texture = this._final_texture;\n\t\t\tif (\n\t\t\t\t!final_texture ||\n\t\t\t\tfinal_texture.width != tex.width ||\n\t\t\t\tfinal_texture.height != tex.height ||\n\t\t\t\tfinal_texture.type != type ||\n\t\t\t\tfinal_texture.format != tex.format\n\t\t\t) {\n\t\t\t\tfinal_texture = this._final_texture = new GL.Texture(\n\t\t\t\t\ttex.width,\n\t\t\t\t\ttex.height,\n\t\t\t\t\t{ type: type, format: tex.format, filter: gl.LINEAR }\n\t\t\t\t);\n\t\t\t}\n\n\t\t}\n\n\t\t//apply FX\n\t\tfx.applyFX(tex, final_texture, glow_texture, average_texture );\n\n\t\tif (this.isOutputConnected(0))\n\t\t\tthis.setOutputData(0, final_texture);\n\n\t\tif (this.isOutputConnected(1))\n\t\t\tthis.setOutputData(1, average_texture);\n\n\t\tif (this.isOutputConnected(2))\n\t\t\tthis.setOutputData(2, glow_texture);\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/glow\", LGraphTextureGlow);\n\n\t// Texture Filter *****************************************\n\tfunction LGraphTextureKuwaharaFilter() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"Filtered\", \"Texture\");\n\t\tthis.properties = { intensity: 1, radius: 5 };\n\t}\n\n\tLGraphTextureKuwaharaFilter.title = \"Kuwahara Filter\";\n\tLGraphTextureKuwaharaFilter.desc =\n\t\t\"Filters a texture giving an artistic oil canvas painting\";\n\n\tLGraphTextureKuwaharaFilter.max_radius = 10;\n\tLGraphTextureKuwaharaFilter._shaders = [];\n\n\tLGraphTextureKuwaharaFilter.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar temp = this._temp_texture;\n\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\tthis._temp_texture = new GL.Texture(tex.width, tex.height, {\n\t\t\t\ttype: tex.type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\t//iterations\n\t\tvar radius = this.properties.radius;\n\t\tradius = Math.min(\n\t\t\tMath.floor(radius),\n\t\t\tLGraphTextureKuwaharaFilter.max_radius\n\t\t);\n\t\tif (radius == 0) {\n\t\t\t//skip blurring\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar intensity = this.properties.intensity;\n\n\t\t//blur sometimes needs an aspect correction\n\t\tvar aspect = LiteGraph.camera_aspect;\n\t\tif (!aspect && window.gl !== undefined) {\n\t\t\taspect = gl.canvas.height / gl.canvas.width;\n\t\t}\n\t\tif (!aspect) {\n\t\t\taspect = 1;\n\t\t}\n\t\taspect = this.properties.preserve_aspect ? aspect : 1;\n\n\t\tif (!LGraphTextureKuwaharaFilter._shaders[radius]) {\n\t\t\tLGraphTextureKuwaharaFilter._shaders[radius] = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureKuwaharaFilter.pixel_shader,\n\t\t\t\t{ RADIUS: radius.toFixed(0) }\n\t\t\t);\n\t\t}\n\n\t\tvar shader = LGraphTextureKuwaharaFilter._shaders[radius];\n\t\tvar mesh = GL.Mesh.getScreenQuad();\n\t\ttex.bind(0);\n\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\tshader\n\t\t\t\t.uniforms({\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tu_intensity: intensity,\n\t\t\t\t\tu_resolution: [tex.width, tex.height],\n\t\t\t\t\tu_iResolution: [1 / tex.width, 1 / tex.height]\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\t//from https://www.shadertoy.com/view/MsXSz4\n\tLGraphTextureKuwaharaFilter.pixel_shader =\n\t\t\"\\n\\\nprecision highp float;\\n\\\nvarying vec2 v_coord;\\n\\\nuniform sampler2D u_texture;\\n\\\nuniform float u_intensity;\\n\\\nuniform vec2 u_resolution;\\n\\\nuniform vec2 u_iResolution;\\n\\\n#ifndef RADIUS\\n\\\n\t#define RADIUS 7\\n\\\n#endif\\n\\\nvoid main() {\\n\\\n\\n\\\n\tconst int radius = RADIUS;\\n\\\n\tvec2 fragCoord = v_coord;\\n\\\n\tvec2 src_size = u_iResolution;\\n\\\n\tvec2 uv = v_coord;\\n\\\n\tfloat n = float((radius + 1) * (radius + 1));\\n\\\n\tint i;\\n\\\n\tint j;\\n\\\n\tvec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\\n\\\n\tvec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\\n\\\n\tvec3 c;\\n\\\n\t\\n\\\n\tfor (int j = -radius; j <= 0; ++j)  {\\n\\\n\t\tfor (int i = -radius; i <= 0; ++i)  {\\n\\\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\\n\\\n\t\t\tm0 += c;\\n\\\n\t\t\ts0 += c * c;\\n\\\n\t\t}\\n\\\n\t}\\n\\\n\t\\n\\\n\tfor (int j = -radius; j <= 0; ++j)  {\\n\\\n\t\tfor (int i = 0; i <= radius; ++i)  {\\n\\\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\\n\\\n\t\t\tm1 += c;\\n\\\n\t\t\ts1 += c * c;\\n\\\n\t\t}\\n\\\n\t}\\n\\\n\t\\n\\\n\tfor (int j = 0; j <= radius; ++j)  {\\n\\\n\t\tfor (int i = 0; i <= radius; ++i)  {\\n\\\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\\n\\\n\t\t\tm2 += c;\\n\\\n\t\t\ts2 += c * c;\\n\\\n\t\t}\\n\\\n\t}\\n\\\n\t\\n\\\n\tfor (int j = 0; j <= radius; ++j)  {\\n\\\n\t\tfor (int i = -radius; i <= 0; ++i)  {\\n\\\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\\n\\\n\t\t\tm3 += c;\\n\\\n\t\t\ts3 += c * c;\\n\\\n\t\t}\\n\\\n\t}\\n\\\n\t\\n\\\n\tfloat min_sigma2 = 1e+2;\\n\\\n\tm0 /= n;\\n\\\n\ts0 = abs(s0 / n - m0 * m0);\\n\\\n\t\\n\\\n\tfloat sigma2 = s0.r + s0.g + s0.b;\\n\\\n\tif (sigma2 < min_sigma2) {\\n\\\n\t\tmin_sigma2 = sigma2;\\n\\\n\t\tgl_FragColor = vec4(m0, 1.0);\\n\\\n\t}\\n\\\n\t\\n\\\n\tm1 /= n;\\n\\\n\ts1 = abs(s1 / n - m1 * m1);\\n\\\n\t\\n\\\n\tsigma2 = s1.r + s1.g + s1.b;\\n\\\n\tif (sigma2 < min_sigma2) {\\n\\\n\t\tmin_sigma2 = sigma2;\\n\\\n\t\tgl_FragColor = vec4(m1, 1.0);\\n\\\n\t}\\n\\\n\t\\n\\\n\tm2 /= n;\\n\\\n\ts2 = abs(s2 / n - m2 * m2);\\n\\\n\t\\n\\\n\tsigma2 = s2.r + s2.g + s2.b;\\n\\\n\tif (sigma2 < min_sigma2) {\\n\\\n\t\tmin_sigma2 = sigma2;\\n\\\n\t\tgl_FragColor = vec4(m2, 1.0);\\n\\\n\t}\\n\\\n\t\\n\\\n\tm3 /= n;\\n\\\n\ts3 = abs(s3 / n - m3 * m3);\\n\\\n\t\\n\\\n\tsigma2 = s3.r + s3.g + s3.b;\\n\\\n\tif (sigma2 < min_sigma2) {\\n\\\n\t\tmin_sigma2 = sigma2;\\n\\\n\t\tgl_FragColor = vec4(m3, 1.0);\\n\\\n\t}\\n\\\n}\\n\\\n\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/kuwahara\",\n\t\tLGraphTextureKuwaharaFilter\n\t);\n\n\t// Texture  *****************************************\n\tfunction LGraphTextureXDoGFilter() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"Filtered\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tsigma: 1.4,\n\t\t\tk: 1.6,\n\t\t\tp: 21.7,\n\t\t\tepsilon: 79,\n\t\t\tphi: 0.017\n\t\t};\n\t}\n\n\tLGraphTextureXDoGFilter.title = \"XDoG Filter\";\n\tLGraphTextureXDoGFilter.desc =\n\t\t\"Filters a texture giving an artistic ink style\";\n\n\tLGraphTextureXDoGFilter.max_radius = 10;\n\tLGraphTextureXDoGFilter._shaders = [];\n\n\tLGraphTextureXDoGFilter.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar temp = this._temp_texture;\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\tthis._temp_texture = new GL.Texture(tex.width, tex.height, {\n\t\t\t\ttype: tex.type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tif (!LGraphTextureXDoGFilter._xdog_shader) {\n\t\t\tLGraphTextureXDoGFilter._xdog_shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureXDoGFilter.xdog_pixel_shader\n\t\t\t);\n\t\t}\n\t\tvar shader = LGraphTextureXDoGFilter._xdog_shader;\n\t\tvar mesh = GL.Mesh.getScreenQuad();\n\n\t\tvar sigma = this.properties.sigma;\n\t\tvar k = this.properties.k;\n\t\tvar p = this.properties.p;\n\t\tvar epsilon = this.properties.epsilon;\n\t\tvar phi = this.properties.phi;\n\t\ttex.bind(0);\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\tshader\n\t\t\t\t.uniforms({\n\t\t\t\t\tsrc: 0,\n\t\t\t\t\tsigma: sigma,\n\t\t\t\t\tk: k,\n\t\t\t\t\tp: p,\n\t\t\t\t\tepsilon: epsilon,\n\t\t\t\t\tphi: phi,\n\t\t\t\t\tcvsWidth: tex.width,\n\t\t\t\t\tcvsHeight: tex.height\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\t//from https://github.com/RaymondMcGuire/GPU-Based-Image-Processing-Tools/blob/master/lib_webgl/scripts/main.js\n\tLGraphTextureXDoGFilter.xdog_pixel_shader =\n\t\t\"\\n\\\nprecision highp float;\\n\\\nuniform sampler2D src;\\n\\n\\\nuniform float cvsHeight;\\n\\\nuniform float cvsWidth;\\n\\n\\\nuniform float sigma;\\n\\\nuniform float k;\\n\\\nuniform float p;\\n\\\nuniform float epsilon;\\n\\\nuniform float phi;\\n\\\nvarying vec2 v_coord;\\n\\n\\\nfloat cosh(float val)\\n\\\n{\\n\\\n\tfloat tmp = exp(val);\\n\\\n\tfloat cosH = (tmp + 1.0 / tmp) / 2.0;\\n\\\n\treturn cosH;\\n\\\n}\\n\\n\\\nfloat tanh(float val)\\n\\\n{\\n\\\n\tfloat tmp = exp(val);\\n\\\n\tfloat tanH = (tmp - 1.0 / tmp) / (tmp + 1.0 / tmp);\\n\\\n\treturn tanH;\\n\\\n}\\n\\n\\\nfloat sinh(float val)\\n\\\n{\\n\\\n\tfloat tmp = exp(val);\\n\\\n\tfloat sinH = (tmp - 1.0 / tmp) / 2.0;\\n\\\n\treturn sinH;\\n\\\n}\\n\\n\\\nvoid main(void){\\n\\\n\tvec3 destColor = vec3(0.0);\\n\\\n\tfloat tFrag = 1.0 / cvsHeight;\\n\\\n\tfloat sFrag = 1.0 / cvsWidth;\\n\\\n\tvec2 Frag = vec2(sFrag,tFrag);\\n\\\n\tvec2 uv = gl_FragCoord.st;\\n\\\n\tfloat twoSigmaESquared = 2.0 * sigma * sigma;\\n\\\n\tfloat twoSigmaRSquared = twoSigmaESquared * k * k;\\n\\\n\tint halfWidth = int(ceil( 1.0 * sigma * k ));\\n\\n\\\n\tconst int MAX_NUM_ITERATION = 99999;\\n\\\n\tvec2 sum = vec2(0.0);\\n\\\n\tvec2 norm = vec2(0.0);\\n\\n\\\n\tfor(int cnt=0;cnt<MAX_NUM_ITERATION;cnt++){\\n\\\n\t\tif(cnt > (2*halfWidth+1)*(2*halfWidth+1)){break;}\\n\\\n\t\tint i = int(cnt / (2*halfWidth+1)) - halfWidth;\\n\\\n\t\tint j = cnt - halfWidth - int(cnt / (2*halfWidth+1)) * (2*halfWidth+1);\\n\\n\\\n\t\tfloat d = length(vec2(i,j));\\n\\\n\t\tvec2 kernel = vec2( exp( -d * d / twoSigmaESquared ), \\n\\\n\t\t\t\t\t\t\texp( -d * d / twoSigmaRSquared ));\\n\\n\\\n\t\tvec2 L = texture2D(src, (uv + vec2(i,j)) * Frag).xx;\\n\\n\\\n\t\tnorm += kernel;\\n\\\n\t\tsum += kernel * L;\\n\\\n\t}\\n\\n\\\n\tsum /= norm;\\n\\n\\\n\tfloat H = 100.0 * ((1.0 + p) * sum.x - p * sum.y);\\n\\\n\tfloat edge = ( H > epsilon )? 1.0 : 1.0 + tanh( phi * (H - epsilon));\\n\\\n\tdestColor = vec3(edge);\\n\\\n\tgl_FragColor = vec4(destColor, 1.0);\\n\\\n}\";\n\n\tLiteGraph.registerNodeType(\"texture/xDoG\", LGraphTextureXDoGFilter);\n\n\t// Texture Webcam *****************************************\n\tfunction LGraphTextureWebcam() {\n\t\tthis.addOutput(\"Webcam\", \"Texture\");\n\t\tthis.properties = { texture_name: \"\", facingMode: \"user\" };\n\t\tthis.boxcolor = \"black\";\n\t\tthis.version = 0;\n\t}\n\n\tLGraphTextureWebcam.title = \"Webcam\";\n\tLGraphTextureWebcam.desc = \"Webcam texture\";\n\n\tLGraphTextureWebcam.is_webcam_open = false;\n\n\tLGraphTextureWebcam.prototype.openStream = function() {\n\t\tif (!navigator.getUserMedia) {\n\t\t\t//console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags');\n\t\t\treturn;\n\t\t}\n\n\t\tthis._waiting_confirmation = true;\n\n\t\t// Not showing vendor prefixes.\n\t\tvar constraints = {\n\t\t\taudio: false,\n\t\t\tvideo: { facingMode: this.properties.facingMode }\n\t\t};\n\t\tnavigator.mediaDevices\n\t\t\t.getUserMedia(constraints)\n\t\t\t.then(this.streamReady.bind(this))\n\t\t\t.catch(onFailSoHard);\n\n\t\tvar that = this;\n\t\tfunction onFailSoHard(e) {\n\t\t\tLGraphTextureWebcam.is_webcam_open = false;\n\t\t\tconsole.log(\"Webcam rejected\", e);\n\t\t\tthat._webcam_stream = false;\n\t\t\tthat.boxcolor = \"red\";\n\t\t\tthat.trigger(\"stream_error\");\n\t\t}\n\t};\n\n\tLGraphTextureWebcam.prototype.closeStream = function() {\n\t\tif (this._webcam_stream) {\n\t\t\tvar tracks = this._webcam_stream.getTracks();\n\t\t\tif (tracks.length) {\n\t\t\t\tfor (var i = 0; i < tracks.length; ++i) {\n\t\t\t\t\ttracks[i].stop();\n\t\t\t\t}\n\t\t\t}\n\t\t\tLGraphTextureWebcam.is_webcam_open = false;\n\t\t\tthis._webcam_stream = null;\n\t\t\tthis._video = null;\n\t\t\tthis.boxcolor = \"black\";\n\t\t\tthis.trigger(\"stream_closed\");\n\t\t}\n\t};\n\n\tLGraphTextureWebcam.prototype.streamReady = function(localMediaStream) {\n\t\tthis._webcam_stream = localMediaStream;\n\t\t//this._waiting_confirmation = false;\n\t\tthis.boxcolor = \"green\";\n\t\tvar video = this._video;\n\t\tif (!video) {\n\t\t\tvideo = document.createElement(\"video\");\n\t\t\tvideo.autoplay = true;\n\t\t\tvideo.srcObject = localMediaStream;\n\t\t\tthis._video = video;\n\t\t\t//document.body.appendChild( video ); //debug\n\t\t\t//when video info is loaded (size and so)\n\t\t\tvideo.onloadedmetadata = function(e) {\n\t\t\t\t// Ready to go. Do some stuff.\n\t\t\t\tLGraphTextureWebcam.is_webcam_open = true;\n\t\t\t\tconsole.log(e);\n\t\t\t};\n\t\t}\n\t\tthis.trigger(\"stream_ready\", video);\n\t};\n\n\tLGraphTextureWebcam.prototype.onPropertyChanged = function(\n\t\tname,\n\t\tvalue\n\t) {\n\t\tif (name == \"facingMode\") {\n\t\t\tthis.properties.facingMode = value;\n\t\t\tthis.closeStream();\n\t\t\tthis.openStream();\n\t\t}\n\t};\n\n\tLGraphTextureWebcam.prototype.onRemoved = function() {\n\t\tif (!this._webcam_stream) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar tracks = this._webcam_stream.getTracks();\n\t\tif (tracks.length) {\n\t\t\tfor (var i = 0; i < tracks.length; ++i) {\n\t\t\t\ttracks[i].stop();\n\t\t\t}\n\t\t}\n\n\t\tthis._webcam_stream = null;\n\t\tthis._video = null;\n\t};\n\n\tLGraphTextureWebcam.prototype.onDrawBackground = function(ctx) {\n\t\tif (this.flags.collapsed || this.size[1] <= 20) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this._video) {\n\t\t\treturn;\n\t\t}\n\n\t\t//render to graph canvas\n\t\tctx.save();\n\t\tif (!ctx.webgl) {\n\t\t\t//reverse image\n\t\t\tctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]);\n\t\t} else {\n\t\t\tif (this._video_texture) {\n\t\t\t\tctx.drawImage(\n\t\t\t\t\tthis._video_texture,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\tthis.size[0],\n\t\t\t\t\tthis.size[1]\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tctx.restore();\n\t};\n\n\tLGraphTextureWebcam.prototype.onExecute = function() {\n\t\tif (this._webcam_stream == null && !this._waiting_confirmation) {\n\t\t\tthis.openStream();\n\t\t}\n\n\t\tif (!this._video || !this._video.videoWidth) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = this._video.videoWidth;\n\t\tvar height = this._video.videoHeight;\n\n\t\tvar temp = this._video_texture;\n\t\tif (!temp || temp.width != width || temp.height != height) {\n\t\t\tthis._video_texture = new GL.Texture(width, height, {\n\t\t\t\tformat: gl.RGB,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tthis._video_texture.uploadImage(this._video);\n\t\tthis._video_texture.version = ++this.version;\n\n\t\tif (this.properties.texture_name) {\n\t\t\tvar container = LGraphTexture.getTexturesContainer();\n\t\t\tcontainer[this.properties.texture_name] = this._video_texture;\n\t\t}\n\n\t\tthis.setOutputData(0, this._video_texture);\n\t\tfor (var i = 1; i < this.outputs.length; ++i) {\n\t\t\tif (!this.outputs[i]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tswitch (this.outputs[i].name) {\n\t\t\t\tcase \"width\":\n\t\t\t\t\tthis.setOutputData(i, this._video.videoWidth);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"height\":\n\t\t\t\t\tthis.setOutputData(i, this._video.videoHeight);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t};\n\n\tLGraphTextureWebcam.prototype.onGetOutputs = function() {\n\t\treturn [\n\t\t\t[\"width\", \"number\"],\n\t\t\t[\"height\", \"number\"],\n\t\t\t[\"stream_ready\", LiteGraph.EVENT],\n\t\t\t[\"stream_closed\", LiteGraph.EVENT],\n\t\t\t[\"stream_error\", LiteGraph.EVENT]\n\t\t];\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/webcam\", LGraphTextureWebcam);\n\n\t//from https://github.com/spite/Wagner\n\tfunction LGraphLensFX() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"f\", \"number\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tenabled: true,\n\t\t\tfactor: 1,\n\t\t\tprecision: LGraphTexture.LOW\n\t\t};\n\n\t\tthis._uniforms = { u_texture: 0, u_factor: 1 };\n\t}\n\n\tLGraphLensFX.title = \"Lens FX\";\n\tLGraphLensFX.desc = \"distortion and chromatic aberration\";\n\n\tLGraphLensFX.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphLensFX.prototype.onGetInputs = function() {\n\t\treturn [[\"enabled\", \"boolean\"]];\n\t};\n\n\tLGraphLensFX.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (\n\t\t\tthis.properties.precision === LGraphTexture.PASS_THROUGH ||\n\t\t\tthis.getInputOrProperty(\"enabled\") === false\n\t\t) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\ttemp = this._temp_texture = new GL.Texture(\n\t\t\t\ttex.width,\n\t\t\t\ttex.height,\n\t\t\t\t{ type: tex.type, format: gl.RGBA, filter: gl.LINEAR }\n\t\t\t);\n\t\t}\n\n\t\tvar shader = LGraphLensFX._shader;\n\t\tif (!shader) {\n\t\t\tshader = LGraphLensFX._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphLensFX.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar factor = this.getInputData(1);\n\t\tif (factor == null) {\n\t\t\tfactor = this.properties.factor;\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_factor = factor;\n\n\t\t//apply shader\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\ttemp.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, temp);\n\t};\n\n\tLGraphLensFX.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_factor;\\n\\\n\t\tvec2 barrelDistortion(vec2 coord, float amt) {\\n\\\n\t\t\tvec2 cc = coord - 0.5;\\n\\\n\t\t\tfloat dist = dot(cc, cc);\\n\\\n\t\t\treturn coord + cc * dist * amt;\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tfloat sat( float t )\\n\\\n\t\t{\\n\\\n\t\t\treturn clamp( t, 0.0, 1.0 );\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tfloat linterp( float t ) {\\n\\\n\t\t\treturn sat( 1.0 - abs( 2.0*t - 1.0 ) );\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tfloat remap( float t, float a, float b ) {\\n\\\n\t\t\treturn sat( (t - a) / (b - a) );\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tvec4 spectrum_offset( float t ) {\\n\\\n\t\t\tvec4 ret;\\n\\\n\t\t\tfloat lo = step(t,0.5);\\n\\\n\t\t\tfloat hi = 1.0-lo;\\n\\\n\t\t\tfloat w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\\n\\\n\t\t\tret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\\n\\\n\t\t\\n\\\n\t\t\treturn pow( ret, vec4(1.0/2.2) );\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tconst float max_distort = 2.2;\\n\\\n\t\tconst int num_iter = 12;\\n\\\n\t\tconst float reci_num_iter_f = 1.0 / float(num_iter);\\n\\\n\t\t\\n\\\n\t\tvoid main()\\n\\\n\t\t{\t\\n\\\n\t\t\tvec2 uv=v_coord;\\n\\\n\t\t\tvec4 sumcol = vec4(0.0);\\n\\\n\t\t\tvec4 sumw = vec4(0.0);\t\\n\\\n\t\t\tfor ( int i=0; i<num_iter;++i )\\n\\\n\t\t\t{\\n\\\n\t\t\t\tfloat t = float(i) * reci_num_iter_f;\\n\\\n\t\t\t\tvec4 w = spectrum_offset( t );\\n\\\n\t\t\t\tsumw += w;\\n\\\n\t\t\t\tsumcol += w * texture2D( u_texture, barrelDistortion(uv, .6 * max_distort*t * u_factor ) );\\n\\\n\t\t\t}\\n\\\n\t\t\tgl_FragColor = sumcol / sumw;\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/lensfx\", LGraphLensFX);\n\n\n\tfunction LGraphTextureFromData() {\n\t\tthis.addInput(\"in\", \"\");\n\t\tthis.properties = { precision: LGraphTexture.LOW, width: 0, height: 0, channels: 1 };\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t}\n\n\tLGraphTextureFromData.title = \"Data->Tex\";\n\tLGraphTextureFromData.desc = \"Generates or applies a curve to a texture\";\n\tLGraphTextureFromData.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureFromData.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar data = this.getInputData(0);\n\t\tif(!data)\n\t\t\treturn;\n\n\t\tvar channels = this.properties.channels;\n\t\tvar w = this.properties.width;\n\t\tvar h = this.properties.height;\n\t\tif(!w || !h)\n\t\t{\n\t\t\tw = Math.floor(data.length / channels);\n\t\t\th = 1;\n\t\t}\n\t\tvar format = gl.RGBA;\n\t\tif( channels == 3 )\n\t\t\tformat = gl.RGB;\n\t\telse if( channels == 1 )\n\t\t\tformat = gl.LUMINANCE;\n\n\t\tvar temp = this._temp_texture;\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision );\n\t\tif ( !temp || temp.width != w || temp.height != h || temp.type != type ) {\n\t\t\ttemp = this._temp_texture = new GL.Texture( w, h, { type: type, format: format, filter: gl.LINEAR } );\n\t\t}\n\n\t\ttemp.uploadData( data );\n\t\tthis.setOutputData(0, temp);\n\t}\n\n\tLiteGraph.registerNodeType(\"texture/fromdata\", LGraphTextureFromData);\n\n\t//applies a curve (or generates one)\n\tfunction LGraphTextureCurve() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = { precision: LGraphTexture.LOW, split_channels: false };\n\t\tthis._values = new Uint8Array(256*4);\n\t\tthis._values.fill(255);\n\t\tthis._curve_texture = null;\n\t\tthis._uniforms = { u_texture: 0, u_curve: 1, u_range: 1.0 };\n\t\tthis._must_update = true;\n\t\tthis._points = {\n\t\t\tRGB: [[0,0],[1,1]],\n\t\t\tR: [[0,0],[1,1]],\n\t\t\tG: [[0,0],[1,1]],\n\t\t\tB: [[0,0],[1,1]]\n\t\t};\n\t\tthis.curve_editor = null;\n\t\tthis.addWidget(\"toggle\",\"Split Channels\",false,\"split_channels\");\n\t\tthis.addWidget(\"combo\",\"Channel\",\"RGB\",{ values:[\"RGB\",\"R\",\"G\",\"B\"]});\n\t\tthis.curve_offset = 68;\n\t\tthis.size = [ 240, 160 ];\n\t}\n\n\tLGraphTextureCurve.title = \"Curve\";\n\tLGraphTextureCurve.desc = \"Generates or applies a curve to a texture\";\n\tLGraphTextureCurve.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureCurve.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\n\t\tvar temp = this._temp_texture;\n\t\tif(!tex) //generate one texture, nothing else\n\t\t{\n\t\t\tif(this._must_update || !this._curve_texture )\n\t\t\t\tthis.updateCurve();\n\t\t\tthis.setOutputData(0, this._curve_texture);\n\t\t\treturn;\n\t\t}\n\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision, tex );\n\t\t\n\t\t//apply curve to input texture\n\t\tif ( !temp || temp.type != type || temp.width != tex.width || temp.height != tex.height || temp.format != tex.format)\n\t\t\ttemp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR } );\n\n\t\tvar shader = LGraphTextureCurve._shader;\n\t\tif (!shader) {\n\t\t\tshader = LGraphTextureCurve._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureCurve.pixel_shader );\n\t\t}\n\n\t\tif(this._must_update || !this._curve_texture )\n\t\t\tthis.updateCurve();\n\n\t\tvar uniforms = this._uniforms;\n\t\tvar curve_texture = this._curve_texture;\n\n\t\t//apply shader\n\t\ttemp.drawTo(function() {\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\ttex.bind(0);\n\t\t\tcurve_texture.bind(1);\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, temp);\n\t}\n\n\tLGraphTextureCurve.prototype.sampleCurve = function(f,points)\n\t{\n\t\tvar points = points || this._points.RGB;\n\t\tif(!points)\n\t\t\treturn;\n\t\tfor(var i = 0; i < points.length - 1; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tvar pn = points[i+1];\n\t\t\tif(pn[0] < f)\n\t\t\t\tcontinue;\n\t\t\tvar r = (pn[0] - p[0]);\n\t\t\tif( Math.abs(r) < 0.00001 )\n\t\t\t\treturn p[1];\n\t\t\tvar local_f = (f - p[0]) / r;\n\t\t\treturn p[1] * (1.0 - local_f) + pn[1] * local_f;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tLGraphTextureCurve.prototype.updateCurve = function()\n\t{\n\t\tvar values = this._values;\n\t\tvar num = values.length / 4;\n\t\tvar split = this.properties.split_channels;\n\t\tfor(var i = 0; i < num; ++i)\n\t\t{\n\t\t\tif(split)\n\t\t\t{\n\t\t\t\tvalues[i*4] = clamp( this.sampleCurve(i/num,this._points.R)*255,0,255);\n\t\t\t\tvalues[i*4+1] = clamp( this.sampleCurve(i/num,this._points.G)*255,0,255);\n\t\t\t\tvalues[i*4+2] = clamp( this.sampleCurve(i/num,this._points.B)*255,0,255);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvar v = this.sampleCurve(i/num);//sample curve\n\t\t\t\tvalues[i*4] = values[i*4+1] = values[i*4+2] = clamp(v*255,0,255);\n\t\t\t}\n\t\t\tvalues[i*4+3] = 255; //alpha fixed\n\t\t}\n\t\tif(!this._curve_texture)\n\t\t\tthis._curve_texture = new GL.Texture(256,1,{ format: gl.RGBA, magFilter: gl.LINEAR, wrap: gl.CLAMP_TO_EDGE });\n\t\tthis._curve_texture.uploadData(values,null,true);\n\t}\n\n\tLGraphTextureCurve.prototype.onSerialize = function(o)\n\t{\n\t\tvar curves = {};\n\t\tfor(var i in this._points)\n\t\t\tcurves[i] = this._points[i].concat();\n\t\to.curves = curves;\n\t}\n\n\tLGraphTextureCurve.prototype.onConfigure = function(o)\n\t{\n\t\tthis._points = o.curves;\n\t\tif(this.curve_editor)\n\t\t\tcurve_editor.points = this._points;\n\t\tthis._must_update = true;\n\t}\n\n\tLGraphTextureCurve.prototype.onMouseDown = function(e, localpos, graphcanvas)\n\t{\n\t\tif(this.curve_editor)\n\t\t{\n\t\t\tvar r = this.curve_editor.onMouseDown([localpos[0],localpos[1]-this.curve_offset], graphcanvas);\n\t\t\tif(r)\n\t\t\t\tthis.captureInput(true);\n\t\t\treturn r;\n\t\t}\n\t}\n\n\tLGraphTextureCurve.prototype.onMouseMove = function(e, localpos, graphcanvas)\n\t{\n\t\tif(this.curve_editor)\n\t\t\treturn this.curve_editor.onMouseMove([localpos[0],localpos[1]-this.curve_offset], graphcanvas);\n\t}\n\n\tLGraphTextureCurve.prototype.onMouseUp = function(e, localpos, graphcanvas)\n\t{\n\t\tif(this.curve_editor)\n\t\t\treturn this.curve_editor.onMouseUp([localpos[0],localpos[1]-this.curve_offset], graphcanvas);\n\t\tthis.captureInput(false);\n\t}\n\n\tLGraphTextureCurve.channel_line_colors = { \"RGB\":\"#666\",\"R\":\"#F33\",\"G\":\"#3F3\",\"B\":\"#33F\" };\n\n\tLGraphTextureCurve.prototype.onDrawBackground = function(ctx, graphcanvas)\n\t{\n\t\tif(this.flags.collapsed)\n\t\t\treturn;\n\n\t\tif(!this.curve_editor)\n\t\t\tthis.curve_editor = new LiteGraph.CurveEditor(this._points.R);\n\t\tctx.save();\n\t\tctx.translate(0,this.curve_offset);\n\t\tvar channel = this.widgets[1].value;\n\n\t\tif(this.properties.split_channels)\n\t\t{\n\t\t\tif(channel == \"RGB\")\n\t\t\t{\n\t\t\t\tthis.widgets[1].value = channel = \"R\";\n\t\t\t\tthis.widgets[1].disabled = false;\n\t\t\t}\n\t\t\tthis.curve_editor.points = this._points.R;\n\t\t\tthis.curve_editor.draw( ctx, [this.size[0],this.size[1] - this.curve_offset], graphcanvas, \"#111\", LGraphTextureCurve.channel_line_colors.R, true );\n\t\t\tctx.globalCompositeOperation = \"lighten\";\n\t\t\tthis.curve_editor.points = this._points.G;\n\t\t\tthis.curve_editor.draw( ctx, [this.size[0],this.size[1] - this.curve_offset], graphcanvas, null, LGraphTextureCurve.channel_line_colors.G, true );\n\t\t\tthis.curve_editor.points = this._points.B;\n\t\t\tthis.curve_editor.draw( ctx, [this.size[0],this.size[1] - this.curve_offset], graphcanvas, null, LGraphTextureCurve.channel_line_colors.B, true );\n\t\t\tctx.globalCompositeOperation = \"source-over\";\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis.widgets[1].value = channel = \"RGB\";\n\t\t\tthis.widgets[1].disabled = true;\n\t\t}\n\n\t\tthis.curve_editor.points = this._points[channel];\n\t\tthis.curve_editor.draw( ctx, [this.size[0],this.size[1] - this.curve_offset], graphcanvas, this.properties.split_channels ? null : \"#111\", LGraphTextureCurve.channel_line_colors[channel]  );\n\t\tctx.restore();\n\t}\n\n\tLGraphTextureCurve.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_curve;\\n\\\n\t\tuniform float u_range;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = texture2D( u_texture, v_coord ) * u_range;\\n\\\n\t\t\tcolor.x = texture2D( u_curve, vec2( color.x, 0.5 ) ).x;\\n\\\n\t\t\tcolor.y = texture2D( u_curve, vec2( color.y, 0.5 ) ).y;\\n\\\n\t\t\tcolor.z = texture2D( u_curve, vec2( color.z, 0.5 ) ).z;\\n\\\n\t\t\t//color.w = texture2D( u_curve, vec2( color.w, 0.5 ) ).w;\\n\\\n\t\t\tgl_FragColor = color;\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/curve\", LGraphTextureCurve);\n\n\t//simple exposition, but plan to expand it to support different gamma curves\n\tfunction LGraphExposition() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"exp\", \"number\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = { exposition: 1, precision: LGraphTexture.LOW };\n\t\tthis._uniforms = { u_texture: 0, u_exposition: 1 };\n\t}\n\n\tLGraphExposition.title = \"Exposition\";\n\tLGraphExposition.desc = \"Controls texture exposition\";\n\n\tLGraphExposition.widgets_info = {\n\t\texposition: { widget: \"slider\", min: 0, max: 3 },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphExposition.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar temp = this._temp_texture;\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\ttemp = this._temp_texture = new GL.Texture(\n\t\t\t\ttex.width,\n\t\t\t\ttex.height,\n\t\t\t\t{ type: tex.type, format: gl.RGBA, filter: gl.LINEAR }\n\t\t\t);\n\t\t}\n\n\t\tvar shader = LGraphExposition._shader;\n\t\tif (!shader) {\n\t\t\tshader = LGraphExposition._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphExposition.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar exp = this.properties.exposition;\n\t\tvar exp_input = this.getInputData(1);\n\t\tif (exp_input != null) {\n\t\t\texp = this.properties.exposition = exp_input;\n\t\t}\n\t\tvar uniforms = this._uniforms;\n\n\t\t//apply shader\n\t\ttemp.drawTo(function() {\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, temp);\n\t};\n\n\tLGraphExposition.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_exposition;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = texture2D( u_texture, v_coord );\\n\\\n\t\t\tgl_FragColor = vec4( color.xyz * u_exposition, color.a );\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/exposition\", LGraphExposition);\n\n\tfunction LGraphToneMapping() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"avg\", \"number,Texture\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tenabled: true,\n\t\t\tscale: 1,\n\t\t\tgamma: 1,\n\t\t\taverage_lum: 1,\n\t\t\tlum_white: 1,\n\t\t\tprecision: LGraphTexture.LOW\n\t\t};\n\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_lumwhite2: 1,\n\t\t\tu_igamma: 1,\n\t\t\tu_scale: 1,\n\t\t\tu_average_lum: 1\n\t\t};\n\t}\n\n\tLGraphToneMapping.title = \"Tone Mapping\";\n\tLGraphToneMapping.desc =\n\t\t\"Applies Tone Mapping to convert from high to low\";\n\n\tLGraphToneMapping.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphToneMapping.prototype.onGetInputs = function() {\n\t\treturn [[\"enabled\", \"boolean\"]];\n\t};\n\n\tLGraphToneMapping.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (\n\t\t\tthis.properties.precision === LGraphTexture.PASS_THROUGH ||\n\t\t\tthis.getInputOrProperty(\"enabled\") === false\n\t\t) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\ttemp = this._temp_texture = new GL.Texture(\n\t\t\t\ttex.width,\n\t\t\t\ttex.height,\n\t\t\t\t{ type: tex.type, format: gl.RGBA, filter: gl.LINEAR }\n\t\t\t);\n\t\t}\n\n\t\tvar avg = this.getInputData(1);\n\t\tif (avg == null) {\n\t\t\tavg = this.properties.average_lum;\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\t\tvar shader = null;\n\n\t\tif (avg.constructor === Number) {\n\t\t\tthis.properties.average_lum = avg;\n\t\t\tuniforms.u_average_lum = this.properties.average_lum;\n\t\t\tshader = LGraphToneMapping._shader;\n\t\t\tif (!shader) {\n\t\t\t\tshader = LGraphToneMapping._shader = new GL.Shader(\n\t\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\tLGraphToneMapping.pixel_shader\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (avg.constructor === GL.Texture) {\n\t\t\tuniforms.u_average_texture = avg.bind(1);\n\t\t\tshader = LGraphToneMapping._shader_texture;\n\t\t\tif (!shader) {\n\t\t\t\tshader = LGraphToneMapping._shader_texture = new GL.Shader(\n\t\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\tLGraphToneMapping.pixel_shader,\n\t\t\t\t\t{ AVG_TEXTURE: \"\" }\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tuniforms.u_lumwhite2 =\n\t\t\tthis.properties.lum_white * this.properties.lum_white;\n\t\tuniforms.u_scale = this.properties.scale;\n\t\tuniforms.u_igamma = 1 / this.properties.gamma;\n\n\t\t//apply shader\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\ttemp.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\tLGraphToneMapping.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_scale;\\n\\\n\t\t#ifdef AVG_TEXTURE\\n\\\n\t\t\tuniform sampler2D u_average_texture;\\n\\\n\t\t#else\\n\\\n\t\t\tuniform float u_average_lum;\\n\\\n\t\t#endif\\n\\\n\t\tuniform float u_lumwhite2;\\n\\\n\t\tuniform float u_igamma;\\n\\\n\t\tvec3 RGB2xyY (vec3 rgb)\\n\\\n\t\t{\\n\\\n\t\t\t const mat3 RGB2XYZ = mat3(0.4124, 0.3576, 0.1805,\\n\\\n\t\t\t\t\t\t\t\t\t   0.2126, 0.7152, 0.0722,\\n\\\n\t\t\t\t\t\t\t\t\t   0.0193, 0.1192, 0.9505);\\n\\\n\t\t\tvec3 XYZ = RGB2XYZ * rgb;\\n\\\n\t\t\t\\n\\\n\t\t\tfloat f = (XYZ.x + XYZ.y + XYZ.z);\\n\\\n\t\t\treturn vec3(XYZ.x / f,\\n\\\n\t\t\t\t\t\tXYZ.y / f,\\n\\\n\t\t\t\t\t\tXYZ.y);\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = texture2D( u_texture, v_coord );\\n\\\n\t\t\tvec3 rgb = color.xyz;\\n\\\n\t\t\tfloat average_lum = 0.0;\\n\\\n\t\t\t#ifdef AVG_TEXTURE\\n\\\n\t\t\t\tvec3 pixel = texture2D(u_average_texture,vec2(0.5)).xyz;\\n\\\n\t\t\t\taverage_lum = (pixel.x + pixel.y + pixel.z) / 3.0;\\n\\\n\t\t\t#else\\n\\\n\t\t\t\taverage_lum = u_average_lum;\\n\\\n\t\t\t#endif\\n\\\n\t\t\t//Ld - this part of the code is the same for both versions\\n\\\n\t\t\tfloat lum = dot(rgb, vec3(0.2126, 0.7152, 0.0722));\\n\\\n\t\t\tfloat L = (u_scale / average_lum) * lum;\\n\\\n\t\t\tfloat Ld = (L * (1.0 + L / u_lumwhite2)) / (1.0 + L);\\n\\\n\t\t\t//first\\n\\\n\t\t\t//vec3 xyY = RGB2xyY(rgb);\\n\\\n\t\t\t//xyY.z *= Ld;\\n\\\n\t\t\t//rgb = xyYtoRGB(xyY);\\n\\\n\t\t\t//second\\n\\\n\t\t\trgb = (rgb / lum) * Ld;\\n\\\n\t\t\trgb = max(rgb,vec3(0.001));\\n\\\n\t\t\trgb = pow( rgb, vec3( u_igamma ) );\\n\\\n\t\t\tgl_FragColor = vec4( rgb, color.a );\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/tonemapping\", LGraphToneMapping);\n\n\tfunction LGraphTexturePerlin() {\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\twidth: 512,\n\t\t\theight: 512,\n\t\t\tseed: 0,\n\t\t\tpersistence: 0.1,\n\t\t\toctaves: 8,\n\t\t\tscale: 1,\n\t\t\toffset: [0, 0],\n\t\t\tamplitude: 1,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t\tthis._key = 0;\n\t\tthis._texture = null;\n\t\tthis._uniforms = {\n\t\t\tu_persistence: 0.1,\n\t\t\tu_seed: 0,\n\t\t\tu_offset: vec2.create(),\n\t\t\tu_scale: 1,\n\t\t\tu_viewport: vec2.create()\n\t\t};\n\t}\n\n\tLGraphTexturePerlin.title = \"Perlin\";\n\tLGraphTexturePerlin.desc = \"Generates a perlin noise texture\";\n\n\tLGraphTexturePerlin.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES },\n\t\twidth: { type: \"number\", precision: 0, step: 1 },\n\t\theight: { type: \"number\", precision: 0, step: 1 },\n\t\toctaves: { type: \"number\", precision: 0, step: 1, min: 1, max: 50 }\n\t};\n\n\tLGraphTexturePerlin.prototype.onGetInputs = function() {\n\t\treturn [\n\t\t\t[\"seed\", \"number\"],\n\t\t\t[\"persistence\", \"number\"],\n\t\t\t[\"octaves\", \"number\"],\n\t\t\t[\"scale\", \"number\"],\n\t\t\t[\"amplitude\", \"number\"],\n\t\t\t[\"offset\", \"vec2\"]\n\t\t];\n\t};\n\n\tLGraphTexturePerlin.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar w = this.properties.width | 0;\n\t\tvar h = this.properties.height | 0;\n\t\tif (w == 0) {\n\t\t\tw = gl.viewport_data[2];\n\t\t} //0 means default\n\t\tif (h == 0) {\n\t\t\th = gl.viewport_data[3];\n\t\t} //0 means default\n\t\tvar type = LGraphTexture.getTextureType(this.properties.precision);\n\n\t\tvar temp = this._texture;\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != w ||\n\t\t\ttemp.height != h ||\n\t\t\ttemp.type != type\n\t\t) {\n\t\t\ttemp = this._texture = new GL.Texture(w, h, {\n\t\t\t\ttype: type,\n\t\t\t\tformat: gl.RGB,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tvar persistence = this.getInputOrProperty(\"persistence\");\n\t\tvar octaves = this.getInputOrProperty(\"octaves\");\n\t\tvar offset = this.getInputOrProperty(\"offset\");\n\t\tvar scale = this.getInputOrProperty(\"scale\");\n\t\tvar amplitude = this.getInputOrProperty(\"amplitude\");\n\t\tvar seed = this.getInputOrProperty(\"seed\");\n\n\t\t//reusing old texture\n\t\tvar key =\n\t\t\t\"\" +\n\t\t\tw +\n\t\t\th +\n\t\t\ttype +\n\t\t\tpersistence +\n\t\t\toctaves +\n\t\t\tscale +\n\t\t\tseed +\n\t\t\toffset[0] +\n\t\t\toffset[1] +\n\t\t\tamplitude;\n\t\tif (key == this._key) {\n\t\t\tthis.setOutputData(0, temp);\n\t\t\treturn;\n\t\t}\n\t\tthis._key = key;\n\n\t\t//gather uniforms\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_persistence = persistence;\n\t\tuniforms.u_octaves = octaves;\n\t\tuniforms.u_offset.set(offset);\n\t\tuniforms.u_scale = scale;\n\t\tuniforms.u_amplitude = amplitude;\n\t\tuniforms.u_seed = seed * 128;\n\t\tuniforms.u_viewport[0] = w;\n\t\tuniforms.u_viewport[1] = h;\n\n\t\t//render\n\t\tvar shader = LGraphTexturePerlin._shader;\n\t\tif (!shader) {\n\t\t\tshader = LGraphTexturePerlin._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTexturePerlin.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\ttemp.drawTo(function() {\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, temp);\n\t};\n\n\tLGraphTexturePerlin.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform vec2 u_offset;\\n\\\n\t\tuniform float u_scale;\\n\\\n\t\tuniform float u_persistence;\\n\\\n\t\tuniform int u_octaves;\\n\\\n\t\tuniform float u_amplitude;\\n\\\n\t\tuniform vec2 u_viewport;\\n\\\n\t\tuniform float u_seed;\\n\\\n\t\t#define M_PI 3.14159265358979323846\\n\\\n\t\t\\n\\\n\t\tfloat rand(vec2 c){\treturn fract(sin(dot(c.xy ,vec2( 12.9898 + u_seed,78.233 + u_seed))) * 43758.5453); }\\n\\\n\t\t\\n\\\n\t\tfloat noise(vec2 p, float freq ){\\n\\\n\t\t\tfloat unit = u_viewport.x/freq;\\n\\\n\t\t\tvec2 ij = floor(p/unit);\\n\\\n\t\t\tvec2 xy = mod(p,unit)/unit;\\n\\\n\t\t\t//xy = 3.*xy*xy-2.*xy*xy*xy;\\n\\\n\t\t\txy = .5*(1.-cos(M_PI*xy));\\n\\\n\t\t\tfloat a = rand((ij+vec2(0.,0.)));\\n\\\n\t\t\tfloat b = rand((ij+vec2(1.,0.)));\\n\\\n\t\t\tfloat c = rand((ij+vec2(0.,1.)));\\n\\\n\t\t\tfloat d = rand((ij+vec2(1.,1.)));\\n\\\n\t\t\tfloat x1 = mix(a, b, xy.x);\\n\\\n\t\t\tfloat x2 = mix(c, d, xy.x);\\n\\\n\t\t\treturn mix(x1, x2, xy.y);\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tfloat pNoise(vec2 p, int res){\\n\\\n\t\t\tfloat persistance = u_persistence;\\n\\\n\t\t\tfloat n = 0.;\\n\\\n\t\t\tfloat normK = 0.;\\n\\\n\t\t\tfloat f = 4.;\\n\\\n\t\t\tfloat amp = 1.0;\\n\\\n\t\t\tint iCount = 0;\\n\\\n\t\t\tfor (int i = 0; i<50; i++){\\n\\\n\t\t\t\tn+=amp*noise(p, f);\\n\\\n\t\t\t\tf*=2.;\\n\\\n\t\t\t\tnormK+=amp;\\n\\\n\t\t\t\tamp*=persistance;\\n\\\n\t\t\t\tif (iCount >= res)\\n\\\n\t\t\t\t\tbreak;\\n\\\n\t\t\t\tiCount++;\\n\\\n\t\t\t}\\n\\\n\t\t\tfloat nf = n/normK;\\n\\\n\t\t\treturn nf*nf*nf*nf;\\n\\\n\t\t}\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec2 uv = v_coord * u_scale * u_viewport + u_offset * u_scale;\\n\\\n\t\t\tvec4 color = vec4( pNoise( uv, u_octaves ) * u_amplitude );\\n\\\n\t\t\tgl_FragColor = color;\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/perlin\", LGraphTexturePerlin);\n\n\tfunction LGraphTextureCanvas2D() {\n\t\tthis.addInput(\"v\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tcode: LGraphTextureCanvas2D.default_code,\n\t\t\twidth: 512,\n\t\t\theight: 512,\n\t\t\tclear: true,\n\t\t\tprecision: LGraphTexture.DEFAULT,\n\t\t\tuse_html_canvas: false\n\t\t};\n\t\tthis._func = null;\n\t\tthis._temp_texture = null;\n\t\tthis.compileCode();\n\t}\n\n\tLGraphTextureCanvas2D.title = \"Canvas2D\";\n\tLGraphTextureCanvas2D.desc = \"Executes Canvas2D code inside a texture or the viewport.\";\n\tLGraphTextureCanvas2D.help = \"Set width and height to 0 to match viewport size.\";\n\n\tLGraphTextureCanvas2D.default_code = \"//vars: canvas,ctx,time\\nctx.fillStyle='red';\\nctx.fillRect(0,0,50,50);\\n\";\n\n\tLGraphTextureCanvas2D.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES },\n\t\tcode: { type: \"code\" },\n\t\twidth: { type: \"number\", precision: 0, step: 1 },\n\t\theight: { type: \"number\", precision: 0, step: 1 }\n\t};\n\n\tLGraphTextureCanvas2D.prototype.onPropertyChanged = function( name, value ) {\n\t\tif (name == \"code\" )\n\t\t\tthis.compileCode( value );\n\t}\n\t\n\tLGraphTextureCanvas2D.prototype.compileCode = function( code ) {\n\t\tthis._func = null;\n\t\tif( !LiteGraph.allow_scripts )\n\t\t\treturn;\n\n\t\ttry {\n\t\t\tthis._func = new Function( \"canvas\", \"ctx\", \"time\", \"script\",\"v\", code );\n\t\t\tthis.boxcolor = \"#00FF00\";\n\t\t} catch (err) {\n\t\t\tthis.boxcolor = \"#FF0000\";\n\t\t\tconsole.error(\"Error parsing script\");\n\t\t\tconsole.error(err);\n\t\t}\n\t};\n\n\tLGraphTextureCanvas2D.prototype.onExecute = function() {\n\t\tvar func = this._func;\n\t\tif (!func || !this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t}\n\t\tthis.executeDraw( func );\n\t}\n\n\tLGraphTextureCanvas2D.prototype.executeDraw = function( func_context ) {\n\n\t\tvar width = this.properties.width || gl.canvas.width;\n\t\tvar height = this.properties.height || gl.canvas.height;\n\t\tvar temp = this._temp_texture;\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision );\n\t\tif (!temp || temp.width != width || temp.height != height || temp.type != type ) {\n\t\t\ttemp = this._temp_texture = new GL.Texture(width, height, {\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR,\n\t\t\t\ttype: type\n\t\t\t});\n\t\t}\n\n\t\tvar v = this.getInputData(0);\n\n\t\tvar properties = this.properties;\n\t\tvar that = this;\n\t\tvar time = this.graph.getTime();\n\t\tvar ctx = gl;\n\t\tvar canvas = gl.canvas;\n\t\tif( this.properties.use_html_canvas || !global.enableWebGLCanvas )\n\t\t{\n\t\t\tif(!this._canvas)\n\t\t\t{\n\t\t\t\tcanvas = this._canvas = createCanvas(width.height);\n\t\t\t\tctx = this._ctx = canvas.getContext(\"2d\");\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tcanvas = this._canvas;\n\t\t\t\tctx = this._ctx;\n\t\t\t}\n\t\t\tcanvas.width = width;\n\t\t\tcanvas.height = height;\n\t\t}\n\n\t\tif(ctx == gl) //using Canvas2DtoWebGL\n\t\t\ttemp.drawTo(function() {\n\t\t\t\tgl.start2D();\n\t\t\t\tif(properties.clear)\n\t\t\t\t{\n\t\t\t\t\tgl.clearColor(0,0,0,0);\n\t\t\t\t\tgl.clear( gl.COLOR_BUFFER_BIT );\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tif (func_context.draw) {\n\t\t\t\t\t\tfunc_context.draw.call(that, canvas, ctx, time, func_context, v);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfunc_context.call(that, canvas, ctx, time, func_context,v);\n\t\t\t\t\t}\n\t\t\t\t\tthat.boxcolor = \"#00FF00\";\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthat.boxcolor = \"#FF0000\";\n\t\t\t\t\tconsole.error(\"Error executing script\");\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t}\n\t\t\t\tgl.finish2D();\n\t\t\t});\n\t\telse //rendering to offscreen canvas and uploading to texture\n\t\t{\n\t\t\tif(properties.clear)\n\t\t\t\tctx.clearRect(0,0,canvas.width,canvas.height);\n\n\t\t\ttry {\n\t\t\t\tif (func_context.draw) {\n\t\t\t\t\tfunc_context.draw.call(this, canvas, ctx, time, func_context, v);\n\t\t\t\t} else {\n\t\t\t\t\tfunc_context.call(this, canvas, ctx, time, func_context,v);\n\t\t\t\t}\n\t\t\t\tthis.boxcolor = \"#00FF00\";\n\t\t\t} catch (err) {\n\t\t\t\tthis.boxcolor = \"#FF0000\";\n\t\t\t\tconsole.error(\"Error executing script\");\n\t\t\t\tconsole.error(err);\n\t\t\t}\n\t\t\ttemp.uploadImage( canvas );\n\t\t}\n\n\t\tthis.setOutputData(0, temp);\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/canvas2D\", LGraphTextureCanvas2D);\n\n\t// To do chroma keying *****************\n\n\tfunction LGraphTextureMatte() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tkey_color: vec3.fromValues(0, 1, 0),\n\t\t\tthreshold: 0.8,\n\t\t\tslope: 0.2,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureMatte.title = \"Matte\";\n\tLGraphTextureMatte.desc = \"Extracts background\";\n\n\tLGraphTextureMatte.widgets_info = {\n\t\tkey_color: { widget: \"color\" },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureMatte.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._tex = LGraphTexture.getTargetTexture(\n\t\t\ttex,\n\t\t\tthis._tex,\n\t\t\tthis.properties.precision\n\t\t);\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tif (!this._uniforms) {\n\t\t\tthis._uniforms = {\n\t\t\t\tu_texture: 0,\n\t\t\t\tu_key_color: this.properties.key_color,\n\t\t\t\tu_threshold: 1,\n\t\t\t\tu_slope: 1\n\t\t\t};\n\t\t}\n\t\tvar uniforms = this._uniforms;\n\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tvar shader = LGraphTextureMatte._shader;\n\t\tif (!shader) {\n\t\t\tshader = LGraphTextureMatte._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureMatte.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tuniforms.u_key_color = this.properties.key_color;\n\t\tuniforms.u_threshold = this.properties.threshold;\n\t\tuniforms.u_slope = this.properties.slope;\n\n\t\tthis._tex.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureMatte.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec3 u_key_color;\\n\\\n\t\tuniform float u_threshold;\\n\\\n\t\tuniform float u_slope;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec3 color = texture2D( u_texture, v_coord ).xyz;\\n\\\n\t\t\tfloat diff = length( normalize(color) - normalize(u_key_color) );\\n\\\n\t\t\tfloat edge = u_threshold * (1.0 - u_slope);\\n\\\n\t\t\tfloat alpha = smoothstep( edge, u_threshold, diff);\\n\\\n\t\t\tgl_FragColor = vec4( color, alpha );\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/matte\", LGraphTextureMatte);\n\n\t//***********************************\n\tfunction LGraphCubemapToTexture2D() {\n\t\tthis.addInput(\"in\", \"texture\");\n\t\tthis.addInput(\"yaw\", \"number\");\n\t\tthis.addOutput(\"out\", \"texture\");\n\t\tthis.properties = { yaw: 0 };\n\t}\n\n\tLGraphCubemapToTexture2D.title = \"CubemapToTexture2D\";\n\tLGraphCubemapToTexture2D.desc = \"Transforms a CUBEMAP texture into a TEXTURE2D in Polar Representation\";\n\n\tLGraphCubemapToTexture2D.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0))\n\t\t\treturn;\n\n\t\tvar tex = this.getInputData(0);\n\t\tif ( !tex || tex.texture_type != GL.TEXTURE_CUBE_MAP )\n\t\t\treturn;\n\t\tif( this._last_tex && ( this._last_tex.height != tex.height || this._last_tex.type != tex.type ))\n\t\t\tthis._last_tex = null;\n\t\tvar yaw = this.getInputOrProperty(\"yaw\");\n\t\tthis._last_tex = GL.Texture.cubemapToTexture2D( tex, tex.height, this._last_tex, true, yaw );\n\t\tthis.setOutputData( 0, this._last_tex );\n\t};\n\n\tLiteGraph.registerNodeType( \"texture/cubemapToTexture2D\", LGraphCubemapToTexture2D );\n})(this);\n\n(function(global) {\r\n\r\n    if (typeof GL == \"undefined\")\r\n\t\treturn;\r\n\r\n    var LiteGraph = global.LiteGraph;\r\n\tvar LGraphCanvas = global.LGraphCanvas;\r\n\r\n\tvar SHADERNODES_COLOR = \"#345\";\r\n\r\n\tvar LGShaders = LiteGraph.Shaders = {};\r\n\r\n\tvar GLSL_types = LGShaders.GLSL_types = [\"float\",\"vec2\",\"vec3\",\"vec4\",\"mat3\",\"mat4\",\"sampler2D\",\"samplerCube\"];\r\n\tvar GLSL_types_const = LGShaders.GLSL_types_const = [\"float\",\"vec2\",\"vec3\",\"vec4\"];\r\n\r\n\tvar GLSL_functions_desc = {\r\n\t\t\"radians\": \"T radians(T degrees)\",\r\n\t\t\"degrees\": \"T degrees(T radians)\",\r\n\t\t\"sin\": \"T sin(T angle)\",\r\n\t\t\"cos\": \"T cos(T angle)\",\r\n\t\t\"tan\": \"T tan(T angle)\",\r\n\t\t\"asin\": \"T asin(T x)\",\r\n\t\t\"acos\": \"T acos(T x)\",\r\n\t\t\"atan\": \"T atan(T x)\",\r\n\t\t\"atan2\": \"T atan(T x,T y)\",\r\n\t\t\"pow\": \"T pow(T x,T y)\",\r\n\t\t\"exp\": \"T exp(T x)\",\r\n\t\t\"log\": \"T log(T x)\",\r\n\t\t\"exp2\": \"T exp2(T x)\",\r\n\t\t\"log2\": \"T log2(T x)\",\r\n\t\t\"sqrt\": \"T sqrt(T x)\",\r\n\t\t\"inversesqrt\": \"T inversesqrt(T x)\",\r\n\t\t\"abs\": \"T abs(T x)\",\r\n\t\t\"sign\": \"T sign(T x)\",\r\n\t\t\"floor\": \"T floor(T x)\",\r\n\t\t\"round\": \"T round(T x)\",\r\n\t\t\"ceil\": \"T ceil(T x)\",\r\n\t\t\"fract\": \"T fract(T x)\",\r\n\t\t\"mod\": \"T mod(T x,T y)\", //\"T mod(T x,float y)\"\r\n\t\t\"min\": \"T min(T x,T y)\",\r\n\t\t\"max\": \"T max(T x,T y)\",\r\n\t\t\"clamp\": \"T clamp(T x,T minVal = 0.0,T maxVal = 1.0)\",\r\n\t\t\"mix\": \"T mix(T x,T y,T a)\", //\"T mix(T x,T y,float a)\"\r\n\t\t\"step\": \"T step(T edge, T edge2, T x)\", //\"T step(float edge, T x)\"\r\n\t\t\"smoothstep\": \"T smoothstep(T edge, T edge2, T x)\", //\"T smoothstep(float edge, T x)\"\r\n\t\t\"length\":\"float length(T x)\",\r\n\t\t\"distance\":\"float distance(T p0, T p1)\",\r\n\t\t\"normalize\":\"T normalize(T x)\",\r\n\t\t\"dot\": \"float dot(T x,T y)\",\r\n\t\t\"cross\": \"vec3 cross(vec3 x,vec3 y)\",\r\n\t\t\"reflect\": \"vec3 reflect(vec3 V,vec3 N)\",\r\n\t\t\"refract\": \"vec3 refract(vec3 V,vec3 N, float IOR)\"\r\n\t};\r\n\r\n\t//parse them\r\n\tvar GLSL_functions = {};\r\n\tvar GLSL_functions_name = [];\r\n\tparseGLSLDescriptions();\r\n\r\n\tLGShaders.ALL_TYPES = \"float,vec2,vec3,vec4\";\r\n\r\n\tfunction parseGLSLDescriptions()\r\n\t{\r\n\t\tGLSL_functions_name.length = 0;\r\n\r\n\t\tfor(var i in GLSL_functions_desc)\r\n\t\t{\r\n\t\t\tvar op = GLSL_functions_desc[i];\r\n\t\t\tvar index = op.indexOf(\" \");\r\n\t\t\tvar return_type = op.substr(0,index);\r\n\t\t\tvar index2 = op.indexOf(\"(\",index);\r\n\t\t\tvar func_name = op.substr(index,index2-index).trim();\r\n\t\t\tvar params = op.substr(index2 + 1, op.length - index2 - 2).split(\",\");\r\n\t\t\tfor(var j in params)\r\n\t\t\t{\r\n\t\t\t\tvar p = params[j].split(\" \").filter(function(a){ return a; });\r\n\t\t\t\tparams[j] = { type: p[0].trim(), name: p[1].trim() };\r\n\t\t\t\tif(p[2] == \"=\")\r\n\t\t\t\t\tparams[j].value = p[3].trim();\r\n\t\t\t}\r\n\t\t\tGLSL_functions[i] = { return_type: return_type, func: func_name, params: params };\r\n\t\t\tGLSL_functions_name.push( func_name );\r\n\t\t\t//console.log( GLSL_functions[i] );\r\n\t\t}\r\n\t}\r\n\r\n\t//common actions to all shader node classes\r\n\tfunction registerShaderNode( type, node_ctor )\r\n\t{\r\n\t\t//static attributes\r\n\t\tnode_ctor.color = SHADERNODES_COLOR;\r\n\t\tnode_ctor.filter = \"shader\";\r\n\r\n\t\t//common methods\r\n\t\tnode_ctor.prototype.clearDestination = function(){ this.shader_destination = {};  }\r\n\t\tnode_ctor.prototype.propagateDestination = function propagateDestination( dest_name )\r\n\t\t{\r\n\t\t\tthis.shader_destination[ dest_name ] = true;\r\n\t\t\tif(this.inputs)\r\n\t\t\tfor(var i = 0; i < this.inputs.length; ++i)\r\n\t\t\t{\r\n\t\t\t\tvar origin_node = this.getInputNode(i);\r\n\t\t\t\tif(origin_node)\r\n\t\t\t\t\torigin_node.propagateDestination( dest_name );\r\n\t\t\t}\r\n\t\t}\r\n\t\tif(!node_ctor.prototype.onPropertyChanged)\r\n\t\t\tnode_ctor.prototype.onPropertyChanged = function()\r\n\t\t\t{\r\n\t\t\t\tif(this.graph)\r\n\t\t\t\t\t this.graph._version++;\r\n\t\t\t}\r\n\r\n\t\t/*\r\n\t\tif(!node_ctor.prototype.onGetCode)\r\n\t\t\tnode_ctor.prototype.onGetCode = function()\r\n\t\t\t{\r\n\t\t\t\t//check destination to avoid lonely nodes\r\n\t\t\t\tif(!this.shader_destination)\r\n\t\t\t\t\treturn;\r\n\t\t\t\t//grab inputs with types\r\n\t\t\t\tvar inputs = [];\r\n\t\t\t\tif(this.inputs)\r\n\t\t\t\tfor(var i = 0; i < this.inputs.length; ++i)\r\n\t\t\t\t\tinputs.push({ type: this.getInputData(i), name: getInputLinkID(this,i) });\r\n\t\t\t\tvar outputs = [];\r\n\t\t\t\tif(this.outputs)\r\n\t\t\t\tfor(var i = 0; i < this.outputs.length; ++i)\r\n\t\t\t\t\toutputs.push({ name: getOutputLinkID(this,i) });\r\n\t\t\t\t//pass to code func\r\n\t\t\t\tvar results = this.extractCode(inputs);\r\n\t\t\t\t//grab output, pass to next\r\n\t\t\t\tif(results)\r\n\t\t\t\tfor(var i = 0; i < results.length; ++i)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar r = results[i];\r\n\t\t\t\t\tif(!r)\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tthis.setOutputData(i,r.value);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t*/\r\n\r\n\t\tLiteGraph.registerNodeType( \"shader::\" + type, node_ctor );\r\n\t}\r\n\r\n\tfunction getShaderNodeVarName( node, name )\r\n\t{\r\n\t\treturn \"VAR_\" + (name || \"TEMP\") + \"_\" + node.id;\r\n\t}\r\n\r\n\tfunction getInputLinkID( node, slot )\r\n\t{\r\n\t\tif(!node.inputs)\r\n\t\t\treturn null;\r\n\t\tvar link = node.getInputLink( slot );\r\n\t\tif( !link )\r\n\t\t\treturn null;\r\n\t\tvar origin_node = node.graph.getNodeById( link.origin_id );\r\n\t\tif( !origin_node )\r\n\t\t\treturn null;\r\n\t\tif(origin_node.getOutputVarName)\r\n\t\t\treturn origin_node.getOutputVarName(link.origin_slot);\r\n\t\t//generate\r\n\t\treturn \"link_\" + origin_node.id + \"_\" + link.origin_slot;\r\n\t}\r\n\r\n\tfunction getOutputLinkID( node, slot )\r\n\t{\r\n\t\tif (!node.isOutputConnected(slot))\r\n\t\t\treturn null;\r\n\t\treturn \"link_\" + node.id + \"_\" + slot;\r\n\t}\r\n\r\n\tLGShaders.registerShaderNode = registerShaderNode;\r\n\tLGShaders.getInputLinkID = getInputLinkID;\r\n\tLGShaders.getOutputLinkID = getOutputLinkID;\r\n\tLGShaders.getShaderNodeVarName = getShaderNodeVarName;\r\n\tLGShaders.parseGLSLDescriptions = parseGLSLDescriptions;\r\n\r\n\t//given a const number, it transform it to a string that matches a type\r\n\tvar valueToGLSL = LiteGraph.valueToGLSL = function valueToGLSL( v, type, precision )\r\n\t{\r\n\t\tvar n = 5; //num decimals\r\n\t\tif(precision != null)\r\n\t\t\tn = precision;\r\n\t\tif(!type)\r\n\t\t{\r\n\t\t\tif(v.constructor === Number)\r\n\t\t\t\ttype = \"float\";\r\n\t\t\telse if(v.length)\r\n\t\t\t{\r\n\t\t\t\tswitch(v.length)\r\n\t\t\t\t{\r\n\t\t\t\t\tcase 2: type = \"vec2\"; break;\r\n\t\t\t\t\tcase 3: type = \"vec3\"; break;\r\n\t\t\t\t\tcase 4: type = \"vec4\"; break;\r\n\t\t\t\t\tcase 9: type = \"mat3\"; break;\r\n\t\t\t\t\tcase 16: type = \"mat4\"; break;\r\n\t\t\t\t\tdefault:\r\n\t\t\t\t\t\tthrow(\"unknown type for glsl value size\");\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tthrow(\"unknown type for glsl value: \" + v.constructor);\r\n\t\t}\r\n\t\tswitch(type)\r\n\t\t{\r\n\t\t\tcase 'float': return v.toFixed(n); break;\r\n\t\t\tcase 'vec2': return \"vec2(\" + v[0].toFixed(n) + \",\" + v[1].toFixed(n) + \")\"; break;\r\n\t\t\tcase 'color3':\r\n\t\t\tcase 'vec3': return \"vec3(\" + v[0].toFixed(n) + \",\" + v[1].toFixed(n) + \",\" + v[2].toFixed(n) + \")\"; break;\r\n\t\t\tcase 'color4':\r\n\t\t\tcase 'vec4': return \"vec4(\" + v[0].toFixed(n) + \",\" + v[1].toFixed(n) + \",\" + v[2].toFixed(n) + \",\" + v[3].toFixed(n) + \")\"; break;\r\n\t\t\tcase 'mat3': return \"mat3(1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0)\"; break; //not fully supported yet\r\n\t\t\tcase 'mat4': return \"mat4(1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0)\"; break;//not fully supported yet\r\n\t\t\tdefault:\r\n\t\t\t\tthrow(\"unknown glsl type in valueToGLSL:\", type);\r\n\t\t}\r\n\r\n\t\treturn \"\";\r\n\t}\r\n\r\n\t//makes sure that a var is of a type, and if not, it converts it\r\n\tvar varToTypeGLSL = LiteGraph.varToTypeGLSL = function varToTypeGLSL( v, input_type, output_type )\r\n\t{\r\n\t\tif(input_type == output_type)\r\n\t\t\treturn v;\r\n\t\tif(v == null)\r\n\t\t\tswitch(output_type)\r\n\t\t\t{\r\n\t\t\t\tcase \"float\": return \"0.0\";\r\n\t\t\t\tcase \"vec2\":  return \"vec2(0.0)\";\r\n\t\t\t\tcase \"vec3\":  return \"vec3(0.0)\";\r\n\t\t\t\tcase \"vec4\":  return \"vec4(0.0,0.0,0.0,1.0)\";\r\n\t\t\t\tdefault: //null\r\n\t\t\t\t\treturn null;\r\n\t\t\t}\r\n\r\n\t\tif(!output_type)\r\n\t\t\tthrow(\"error: no output type specified\");\r\n\t\tif(output_type == \"float\")\r\n\t\t{\r\n\t\t\tswitch(input_type)\r\n\t\t\t{\r\n\t\t\t\t//case \"float\":\r\n\t\t\t\tcase \"vec2\":\r\n\t\t\t\tcase \"vec3\":\r\n\t\t\t\tcase \"vec4\":\r\n\t\t\t\t\treturn v + \".x\";\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tdefault: //null\r\n\t\t\t\t\treturn \"0.0\";\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if(output_type == \"vec2\")\r\n\t\t{\r\n\t\t\tswitch(input_type)\r\n\t\t\t{\r\n\t\t\t\tcase \"float\":\r\n\t\t\t\t\treturn \"vec2(\"+v+\")\";\r\n\t\t\t\t//case \"vec2\":\r\n\t\t\t\tcase \"vec3\":\r\n\t\t\t\tcase \"vec4\":\r\n\t\t\t\t\treturn v + \".xy\";\r\n\t\t\t\tdefault: //null\r\n\t\t\t\t\treturn \"vec2(0.0)\";\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if(output_type == \"vec3\")\r\n\t\t{\r\n\t\t\tswitch(input_type)\r\n\t\t\t{\r\n\t\t\t\tcase \"float\":\r\n\t\t\t\t\treturn \"vec3(\"+v+\")\";\r\n\t\t\t\tcase \"vec2\":\r\n\t\t\t\t\treturn \"vec3(\" + v + \",0.0)\";\r\n\t\t\t\t//case \"vec3\":\r\n\t\t\t\tcase \"vec4\":\r\n\t\t\t\t\treturn v + \".xyz\";\r\n\t\t\t\tdefault: //null\r\n\t\t\t\t\treturn \"vec3(0.0)\";\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if(output_type == \"vec4\")\r\n\t\t{\r\n\t\t\tswitch(input_type)\r\n\t\t\t{\r\n\t\t\t\tcase \"float\":\r\n\t\t\t\t\treturn \"vec4(\"+v+\")\";\r\n\t\t\t\tcase \"vec2\":\r\n\t\t\t\t\treturn \"vec4(\" + v + \",0.0,1.0)\";\r\n\t\t\t\tcase \"vec3\":\r\n\t\t\t\t\treturn \"vec4(\" + v + \",1.0)\";\r\n\t\t\t\tdefault: //null\r\n\t\t\t\t\treturn \"vec4(0.0,0.0,0.0,1.0)\";\r\n\t\t\t}\r\n\t\t}\r\n\t\tthrow(\"type cannot be converted\");\r\n\t}\r\n\r\n\r\n\t//used to plug incompatible stuff\r\n\tvar convertVarToGLSLType = LiteGraph.convertVarToGLSLType = function convertVarToGLSLType( varname, type, target_type )\r\n\t{\r\n\t\tif(type == target_type)\r\n\t\t\treturn varname;\r\n\t\tif(type == \"float\")\r\n\t\t\treturn target_type + \"(\" + varname + \")\";\r\n\t\tif(target_type == \"vec2\") //works for vec2,vec3 and vec4\r\n\t\t\treturn \"vec2(\" + varname + \".xy)\";\r\n\t\tif(target_type == \"vec3\") //works for vec2,vec3 and vec4\r\n\t\t{\r\n\t\t\tif(type == \"vec2\")\r\n\t\t\t\treturn \"vec3(\" + varname + \",0.0)\";\r\n\t\t\tif(type == \"vec4\")\r\n\t\t\t\treturn \"vec4(\" + varname + \".xyz)\";\r\n\t\t}\r\n\t\tif(target_type == \"vec4\")\r\n\t\t{\r\n\t\t\tif(type == \"vec2\")\r\n\t\t\t\treturn \"vec4(\" + varname + \",0.0,0.0)\";\r\n\t\t\tif(target_type == \"vec3\")\r\n\t\t\t\treturn \"vec4(\" + varname + \",1.0)\";\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\t//used to host a shader body **************************************\r\n\tfunction LGShaderContext()\r\n\t{\r\n\t\t//to store the code template\r\n\t\tthis.vs_template = \"\";\r\n\t\tthis.fs_template = \"\";\r\n\r\n\t\t//required so nodes now where to fetch the input data\r\n\t\tthis.buffer_names = {\r\n\t\t\tuvs: \"v_coord\"\r\n\t\t};\r\n\r\n\t\tthis.extra = {}; //to store custom info from the nodes (like if this shader supports a feature, etc)\r\n\r\n\t\tthis._functions = {};\r\n\t\tthis._uniforms = {};\r\n\t\tthis._codeparts = {};\r\n\t\tthis._uniform_value = null;\r\n\t}\r\n\r\n\tLGShaderContext.prototype.clear = function()\r\n\t{\r\n\t\tthis._uniforms = {};\r\n\t\tthis._functions = {};\r\n\t\tthis._codeparts = {};\r\n\t\tthis._uniform_value = null;\r\n\r\n\t\tthis.extra = {};\r\n\t}\r\n\r\n\tLGShaderContext.prototype.addUniform = function( name, type, value )\r\n\t{\r\n\t\tthis._uniforms[ name ] = type;\r\n\t\tif(value != null)\r\n\t\t{\r\n\t\t\tif(!this._uniform_value)\r\n\t\t\t\tthis._uniform_value = {};\r\n\t\t\tthis._uniform_value[name] = value;\r\n\t\t}\r\n\t}\r\n\r\n\tLGShaderContext.prototype.addFunction = function( name, code )\r\n\t{\r\n\t\tthis._functions[name] = code;\r\n\t}\r\n\r\n\tLGShaderContext.prototype.addCode = function( hook, code, destinations )\r\n\t{\r\n\t\tdestinations = destinations || {\"\":\"\"};\r\n\t\tfor(var i in destinations)\r\n\t\t{\r\n\t\t\tvar h = i ? i + \"_\" + hook : hook;\r\n\t\t\tif(!this._codeparts[ h ])\r\n\t\t\t\tthis._codeparts[ h ] = code + \"\\n\";\r\n\t\t\telse\r\n\t\t\t\tthis._codeparts[ h ] += code + \"\\n\";\r\n\t\t}\r\n\t}\r\n\r\n\t//the system works by grabbing code fragments from every node and concatenating them in blocks depending on where must they be attached\r\n\tLGShaderContext.prototype.computeCodeBlocks = function( graph, extra_uniforms )\r\n\t{\r\n\t\t//prepare context\r\n\t\tthis.clear();\r\n\r\n\t\t//grab output nodes\r\n\t\tvar vertexout = graph.findNodesByType(\"shader::output/vertex\");\r\n\t\tvertexout = vertexout && vertexout.length ? vertexout[0] : null;\r\n\t\tvar fragmentout = graph.findNodesByType(\"shader::output/fragcolor\");\r\n\t\tfragmentout = fragmentout && fragmentout.length ? fragmentout[0] : null;\r\n\t\tif(!fragmentout) //??\r\n\t\t\treturn null; \r\n\r\n\t\t//propagate back destinations\r\n\t\tgraph.sendEventToAllNodes( \"clearDestination\" );\r\n\t\tif(vertexout)\r\n\t\t\tvertexout.propagateDestination(\"vs\");\r\n\t\tif(fragmentout)\r\n\t\t\tfragmentout.propagateDestination(\"fs\");\r\n\r\n\t\t//gets code from graph\r\n\t\tgraph.sendEventToAllNodes(\"onGetCode\", this );\r\n\r\n\t\tvar uniforms = \"\";\r\n\t\tfor(var i in this._uniforms)\r\n\t\t\tuniforms += \"uniform \" + this._uniforms[i] + \" \" + i + \";\\n\";\r\n\t\tif(extra_uniforms)\r\n\t\t\tfor(var i in extra_uniforms)\r\n\t\t\t\tuniforms += \"uniform \" + extra_uniforms[i] + \" \" + i + \";\\n\";\r\n\r\n\t\tvar functions = \"\";\r\n\t\tfor(var i in this._functions)\r\n\t\t\tfunctions += \"//\" + i + \"\\n\" + this._functions[i] + \"\\n\";\r\n\r\n\t\tvar blocks = this._codeparts;\r\n\t\tblocks.uniforms = uniforms;\r\n\t\tblocks.functions = functions;\r\n\t\treturn blocks;\r\n\t}\r\n\r\n\t//replaces blocks using the vs and fs template and returns the final codes\r\n\tLGShaderContext.prototype.computeShaderCode = function( graph )\r\n\t{\r\n\t\tvar blocks = this.computeCodeBlocks( graph );\r\n\t\tvar vs_code = GL.Shader.replaceCodeUsingContext( this.vs_template, blocks );\r\n\t\tvar fs_code = GL.Shader.replaceCodeUsingContext( this.fs_template, blocks );\r\n\t\treturn {\r\n\t\t\tvs_code: vs_code,\r\n\t\t\tfs_code: fs_code\r\n\t\t};\r\n\t}\r\n\r\n\t//generates the shader code from the template and the \r\n\tLGShaderContext.prototype.computeShader = function( graph, shader )\r\n\t{\r\n\t\tvar finalcode = this.computeShaderCode( graph );\r\n\t\tconsole.log( finalcode.vs_code, finalcode.fs_code );\r\n\r\n\t\tif(!LiteGraph.catch_exceptions)\r\n\t\t{\r\n\t\t\tthis._shader_error = true;\r\n\t\t\tif(shader)\r\n\t\t\t\tshader.updateShader( finalcode.vs_code, finalcode.fs_code );\r\n\t\t\telse\r\n\t\t\t\tshader = new GL.Shader( finalcode.vs_code, finalcode.fs_code );\r\n\t\t\tthis._shader_error = false;\r\n\t\t\treturn shader;\r\n\t\t}\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif(shader)\r\n\t\t\t\tshader.updateShader( finalcode.vs_code, finalcode.fs_code );\r\n\t\t\telse\r\n\t\t\t\tshader = new GL.Shader( finalcode.vs_code, finalcode.fs_code );\r\n\t\t\tthis._shader_error = false;\r\n\t\t\treturn shader;\r\n\t\t}\r\n\t\tcatch (err)\r\n\t\t{\r\n\t\t\tif(!this._shader_error)\r\n\t\t\t{\r\n\t\t\t\tconsole.error(err);\r\n\t\t\t\tif(err.indexOf(\"Fragment shader\") != -1)\r\n\t\t\t\t\tconsole.log( finalcode.fs_code.split(\"\\n\").map(function(v,i){ return i + \".- \" + v; }).join(\"\\n\") );\r\n\t\t\t\telse\r\n\t\t\t\t\tconsole.log( finalcode.vs_code );\r\n\t\t\t}\r\n\t\t\tthis._shader_error = true;\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\treturn null;//never here\r\n\t}\r\n\r\n\tLGShaderContext.prototype.getShader = function( graph )\r\n\t{\r\n\t\t//if graph not changed?\r\n\t\tif(this._shader && this._shader._version == graph._version)\r\n\t\t\treturn this._shader;\r\n\r\n\t\t//compile shader\r\n\t\tvar shader = this.computeShader( graph, this._shader );\r\n\t\tif(!shader)\r\n\t\t\treturn null;\r\n\t\t\r\n\t\tthis._shader = shader;\r\n\t\tshader._version = graph._version;\r\n\t\treturn shader;\r\n\t}\r\n\r\n\t//some shader nodes could require to fill the box with some uniforms\r\n\tLGShaderContext.prototype.fillUniforms = function( uniforms, param )\r\n\t{\r\n\t\tif(!this._uniform_value)\r\n\t\t\treturn;\r\n\r\n\t\tfor(var i in this._uniform_value)\r\n\t\t{\r\n\t\t\tvar v = this._uniform_value[i];\r\n\t\t\tif(v == null)\r\n\t\t\t\tcontinue;\r\n\t\t\tif(v.constructor === Function)\r\n\t\t\t\tuniforms[i] = v.call( this, param );\r\n\t\t\telse if(v.constructor === GL.Texture)\r\n\t\t\t{\r\n\t\t\t\t//todo...\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tuniforms[i] = v;\r\n\t\t}\r\n\t}\r\n\r\n\tLiteGraph.ShaderContext = LiteGraph.Shaders.Context = LGShaderContext;\r\n\r\n\t// LGraphShaderGraph *****************************\r\n\t// applies a shader graph to texture, it can be uses as an example\r\n\r\n\tfunction LGraphShaderGraph() {\r\n\r\n\t\t//before inputs\r\n        this.subgraph = new LiteGraph.LGraph();\r\n        this.subgraph._subgraph_node = this;\r\n        this.subgraph._is_subgraph = true;\r\n\t\tthis.subgraph.filter = \"shader\";\r\n\r\n\t\tthis.addInput(\"in\", \"texture\");\r\n\t\tthis.addOutput(\"out\", \"texture\");\r\n\t\tthis.properties = { width: 0, height: 0, alpha: false, precision: typeof(LGraphTexture) != \"undefined\" ? LGraphTexture.DEFAULT : 2 };\r\n\r\n\t\tvar inputNode = this.subgraph.findNodesByType(\"shader::input/uniform\")[0];\r\n\t\tinputNode.pos = [200,300];\r\n\r\n\t\tvar sampler = LiteGraph.createNode(\"shader::texture/sampler2D\");\r\n\t\tsampler.pos = [400,300];\r\n\t\tthis.subgraph.add( sampler );\r\n\r\n\t\tvar outnode = LiteGraph.createNode(\"shader::output/fragcolor\");\r\n\t\toutnode.pos = [600,300];\r\n\t\tthis.subgraph.add( outnode );\r\n\r\n\t\tinputNode.connect( 0, sampler );\r\n\t\tsampler.connect( 0, outnode );\r\n\r\n\t\tthis.size = [180,60];\r\n\t\tthis.redraw_on_mouse = true; //force redraw\r\n\r\n\t\tthis._uniforms = {};\r\n\t\tthis._shader = null;\r\n\t\tthis._context = new LGShaderContext();\r\n\t\tthis._context.vs_template = \"#define VERTEX\\n\" + GL.Shader.SCREEN_VERTEX_SHADER;\r\n\t\tthis._context.fs_template = LGraphShaderGraph.template;\r\n\t}\r\n\r\n\tLGraphShaderGraph.template = \"\\n\\\r\n#define FRAGMENT\\n\\\r\nprecision highp float;\\n\\\r\nvarying vec2 v_coord;\\n\\\r\n{{varying}}\\n\\\r\n{{uniforms}}\\n\\\r\n{{functions}}\\n\\\r\n{{fs_functions}}\\n\\\r\nvoid main() {\\n\\n\\\r\nvec2 uv = v_coord;\\n\\\r\nvec4 fragcolor = vec4(0.0);\\n\\\r\nvec4 fragcolor1 = vec4(0.0);\\n\\\r\n{{fs_code}}\\n\\\r\ngl_FragColor = fragcolor;\\n\\\r\n}\\n\\\r\n\t\";\r\n\r\n\tLGraphShaderGraph.widgets_info = {\r\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\r\n\t};\r\n\r\n\tLGraphShaderGraph.title = \"ShaderGraph\";\r\n\tLGraphShaderGraph.desc = \"Builds a shader using a graph\";\r\n\tLGraphShaderGraph.input_node_type = \"input/uniform\";\r\n\tLGraphShaderGraph.output_node_type = \"output/fragcolor\";\r\n\tLGraphShaderGraph.title_color = SHADERNODES_COLOR;\r\n\r\n\tLGraphShaderGraph.prototype.onSerialize = function(o)\r\n\t{\r\n\t\to.subgraph = this.subgraph.serialize();\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.onConfigure = function(o)\r\n\t{\r\n\t\tthis.subgraph.configure(o.subgraph);\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.onExecute = function() {\r\n\t\tif (!this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\t//read input texture\r\n\t\tvar intex = this.getInputData(0);\r\n\t\tif(intex && intex.constructor != GL.Texture)\r\n\t\t\tintex = null;\r\n\r\n\t\tvar w = this.properties.width | 0;\r\n\t\tvar h = this.properties.height | 0;\r\n\t\tif (w == 0) {\r\n\t\t\tw = intex ? intex.width : gl.viewport_data[2];\r\n\t\t} //0 means default\r\n\t\tif (h == 0) {\r\n\t\t\th = intex ? intex.height : gl.viewport_data[3];\r\n\t\t} //0 means default\r\n\r\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision, intex );\r\n\r\n\t\tvar texture = this._texture;\r\n\t\tif ( !texture || texture.width != w || texture.height != h || texture.type != type ) {\r\n\t\t\ttexture = this._texture = new GL.Texture(w, h, {\r\n\t\t\t\ttype: type,\r\n\t\t\t\tformat: this.alpha ? gl.RGBA : gl.RGB,\r\n\t\t\t\tfilter: gl.LINEAR\r\n\t\t\t});\r\n\t\t}\r\n\t\t\r\n\t\tvar shader = this.getShader( this.subgraph );\r\n\t\tif(!shader)\r\n\t\t\treturn;\r\n\r\n\t\tvar uniforms = this._uniforms;\r\n\t\tthis._context.fillUniforms( uniforms );\r\n\r\n\t\tvar tex_slot = 0;\r\n\t\tif(this.inputs)\r\n\t\tfor(var i = 0; i < this.inputs.length; ++i)\r\n\t\t{\r\n\t\t\tvar input = this.inputs[i];\r\n\t\t\tvar data = this.getInputData(i);\r\n\t\t\tif(input.type == \"texture\")\r\n\t\t\t{\r\n\t\t\t\tif(!data)\r\n\t\t\t\t\tdata = GL.Texture.getWhiteTexture();\r\n\t\t\t\tdata = data.bind(tex_slot++);\r\n\t\t\t}\r\n\r\n\t\t\tif(data != null)\r\n\t\t\t\tuniforms[ \"u_\" + input.name ] = data;\r\n\t\t}\r\n\r\n\t\tvar mesh = GL.Mesh.getScreenQuad();\r\n\r\n\t\tgl.disable( gl.DEPTH_TEST );\r\n\t\tgl.disable( gl.BLEND );\r\n\r\n\t\ttexture.drawTo(function(){\r\n\t\t\tshader.uniforms( uniforms );\r\n\t\t\tshader.draw( mesh );\r\n\t\t});\r\n\r\n\t\t//use subgraph output \r\n\t\tthis.setOutputData(0, texture );\r\n\t};\r\n\r\n\t//add input node inside subgraph\r\n\tLGraphShaderGraph.prototype.onInputAdded = function( slot_info )\r\n\t{\r\n\t\tvar subnode = LiteGraph.createNode(\"shader::input/uniform\");\r\n\t\tsubnode.setProperty(\"name\",slot_info.name);\r\n\t\tsubnode.setProperty(\"type\",slot_info.type);\r\n\t\tthis.subgraph.add( subnode );\r\n\t}\r\n\r\n\t//remove all\r\n\tLGraphShaderGraph.prototype.onInputRemoved = function( slot, slot_info )\r\n\t{\r\n\t\tvar nodes = this.subgraph.findNodesByType(\"shader::input/uniform\");\r\n\t\tfor(var i = 0; i < nodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar node = nodes[i];\r\n\t\t\tif(node.properties.name == slot_info.name )\r\n\t\t\t\tthis.subgraph.remove( node );\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.computeSize = function()\r\n\t{\r\n\t\tvar num_inputs = this.inputs ? this.inputs.length : 0;\r\n\t\tvar num_outputs = this.outputs ? this.outputs.length : 0;\r\n\t\treturn [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT + 10];\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.getShader = function()\r\n\t{\r\n\t\tvar shader = this._context.getShader( this.subgraph );\r\n\t\tif(!shader)\r\n\t\t\tthis.boxcolor = \"red\";\r\n\t\telse\r\n\t\t\tthis.boxcolor = null;\r\n\t\treturn shader;\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.onDrawBackground = function(ctx, graphcanvas, canvas, pos)\r\n\t{\r\n\t\tif(this.flags.collapsed)\r\n\t\t\treturn;\r\n\r\n\t\t//allows to preview the node if the canvas is a webgl canvas\r\n\t\tvar tex = this.getOutputData(0);\r\n\t\tvar inputs_y = this.inputs ? this.inputs.length * LiteGraph.NODE_SLOT_HEIGHT : 0;\r\n\t\tif (tex && ctx == tex.gl && this.size[1] > inputs_y + LiteGraph.NODE_TITLE_HEIGHT ) {\r\n\t\t\tctx.drawImage( tex, 10,y, this.size[0] - 20, this.size[1] - inputs_y - LiteGraph.NODE_TITLE_HEIGHT );\r\n\t\t}\r\n\r\n\t\tvar y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n\r\n\t\t//button\r\n\t\tvar over = LiteGraph.isInsideRectangle(pos[0],pos[1],this.pos[0],this.pos[1] + y,this.size[0],LiteGraph.NODE_TITLE_HEIGHT);\r\n\t\tctx.fillStyle = over ? \"#555\" : \"#222\";\r\n\t\tctx.beginPath();\r\n\t\tif (this._shape == LiteGraph.BOX_SHAPE)\r\n\t\t\tctx.rect(0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT);\r\n\t\telse\r\n\t\t\tctx.roundRect( 0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT, 0, 8);\r\n\t\tctx.fill();\r\n\r\n\t\t//button\r\n\t\tctx.textAlign = \"center\";\r\n\t\tctx.font = \"24px Arial\";\r\n\t\tctx.fillStyle = over ? \"#DDD\" : \"#999\";\r\n\t\tctx.fillText( \"+\", this.size[0] * 0.5, y + 24 );\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.onMouseDown = function(e, localpos, graphcanvas)\r\n\t{\r\n\t\tvar y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n\t\tif(localpos[1] > y)\r\n\t\t{\r\n\t\t\tgraphcanvas.showSubgraphPropertiesDialog(this);\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.onDrawSubgraphBackground = function(graphcanvas)\r\n\t{\r\n\t\t//TODO\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.getExtraMenuOptions = function(graphcanvas)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tvar options = [{ content: \"Print Code\", callback: function(){\r\n\t\t\tvar code = that._context.computeShaderCode();\r\n\t\t\tconsole.log( code.vs_code, code.fs_code );\r\n\t\t}}];\r\n\r\n\t\treturn options;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"texture/shaderGraph\", LGraphShaderGraph );\r\n\r\n\tfunction shaderNodeFromFunction( classname, params, return_type, code )\r\n\t{\r\n\t\t//TODO\r\n\t}\r\n\r\n\t//Shader Nodes ***********************************************************\r\n\r\n\t//applies a shader graph to a code\r\n\tfunction LGraphShaderUniform() {\r\n\t\tthis.addOutput(\"out\", \"\");\r\n\t\tthis.properties = { name: \"\", type: \"\" };\r\n\t}\r\n\r\n\tLGraphShaderUniform.title = \"Uniform\";\r\n\tLGraphShaderUniform.desc = \"Input data for the shader\";\r\n\r\n\tLGraphShaderUniform.prototype.getTitle = function()\r\n\t{\r\n\t\tif( this.properties.name && this.flags.collapsed)\r\n\t\t\treturn this.properties.type + \" \" + this.properties.name;\r\n\t\treturn \"Uniform\";\r\n\t}\r\n\r\n\tLGraphShaderUniform.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tthis.outputs[0].name = this.properties.type + \" \" + this.properties.name;\r\n\t}\r\n\r\n\tLGraphShaderUniform.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar type = this.properties.type;\r\n\t\tif( !type )\r\n\t\t{\r\n\t\t\tif( !context.onGetPropertyInfo )\r\n\t\t\t\treturn;\r\n\t\t\tvar info = context.onGetPropertyInfo( this.property.name );\r\n\t\t\tif(!info)\r\n\t\t\t\treturn;\r\n\t\t\ttype = info.type;\r\n\t\t}\r\n\t\tif(type == \"number\")\r\n\t\t\ttype = \"float\";\r\n\t\telse if(type == \"texture\")\r\n\t\t\ttype = \"sampler2D\";\r\n\t\tif ( LGShaders.GLSL_types.indexOf(type) == -1 )\r\n\t\t\treturn;\r\n\r\n\t\tcontext.addUniform( \"u_\" + this.properties.name, type );\r\n\t\tthis.setOutputData( 0, type );\r\n\t}\r\n\r\n\tLGraphShaderUniform.prototype.getOutputVarName = function(slot)\r\n\t{\r\n\t\treturn \"u_\" + this.properties.name;\r\n\t}\r\n\r\n\tregisterShaderNode( \"input/uniform\", LGraphShaderUniform );\r\n\r\n\r\n\tfunction LGraphShaderAttribute() {\r\n\t\tthis.addOutput(\"out\", \"vec2\");\r\n\t\tthis.properties = { name: \"coord\", type: \"vec2\" };\r\n\t}\r\n\r\n\tLGraphShaderAttribute.title = \"Attribute\";\r\n\tLGraphShaderAttribute.desc = \"Input data from mesh attribute\";\r\n\r\n\tLGraphShaderAttribute.prototype.getTitle = function()\r\n\t{\r\n\t\treturn \"att. \" + this.properties.name;\r\n\t}\r\n\r\n\tLGraphShaderAttribute.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar type = this.properties.type;\r\n\t\tif( !type || LGShaders.GLSL_types.indexOf(type) == -1 )\r\n\t\t\treturn;\r\n\t\tif(type == \"number\")\r\n\t\t\ttype = \"float\";\r\n\t\tif( this.properties.name != \"coord\")\r\n\t\t{\r\n\t\t\tcontext.addCode( \"varying\", \" varying \" + type +\" v_\" + this.properties.name + \";\" );\r\n\t\t\t//if( !context.varyings[ this.properties.name ] )\r\n\t\t\t//context.addCode( \"vs_code\", \"v_\" + this.properties.name + \" = \" + input_name + \";\" );\r\n\t\t}\r\n\t\tthis.setOutputData( 0, type );\r\n\t}\r\n\r\n\tLGraphShaderAttribute.prototype.getOutputVarName = function(slot)\r\n\t{\r\n\t\treturn \"v_\" + this.properties.name;\r\n\t}\r\n\r\n\tregisterShaderNode( \"input/attribute\", LGraphShaderAttribute );\r\n\r\n\tfunction LGraphShaderSampler2D() {\r\n\t\tthis.addInput(\"tex\", \"sampler2D\");\r\n\t\tthis.addInput(\"uv\", \"vec2\");\r\n\t\tthis.addOutput(\"rgba\", \"vec4\");\r\n\t\tthis.addOutput(\"rgb\", \"vec3\");\r\n\t}\r\n\r\n\tLGraphShaderSampler2D.title = \"Sampler2D\";\r\n\tLGraphShaderSampler2D.desc = \"Reads a pixel from a texture\";\r\n\r\n\tLGraphShaderSampler2D.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar texname = getInputLinkID( this, 0 );\r\n\t\tvar varname = getShaderNodeVarName(this);\r\n\t\tvar code = \"vec4 \" + varname + \" = vec4(0.0);\\n\";\r\n\t\tif(texname)\r\n\t\t{\r\n\t\t\tvar uvname = getInputLinkID( this, 1 ) || context.buffer_names.uvs;\r\n\t\t\tcode += varname + \" = texture2D(\"+texname+\",\"+uvname+\");\\n\";\r\n\t\t}\r\n\r\n\t\tvar link0 = getOutputLinkID( this, 0 );\r\n\t\tif(link0)\r\n\t\t\tcode += \"vec4 \" + getOutputLinkID( this, 0 ) + \" = \"+varname+\";\\n\";\r\n\r\n\t\tvar link1 = getOutputLinkID( this, 1 );\r\n\t\tif(link1)\r\n\t\t\tcode += \"vec3 \" + getOutputLinkID( this, 1 ) + \" = \"+varname+\".xyz;\\n\";\r\n\r\n\t\tcontext.addCode( \"code\", code, this.shader_destination );\r\n\t\tthis.setOutputData( 0, \"vec4\" );\r\n\t\tthis.setOutputData( 1, \"vec3\" );\r\n\t}\r\n\r\n\tregisterShaderNode( \"texture/sampler2D\", LGraphShaderSampler2D );\r\n\r\n\t//*********************************\r\n\r\n\tfunction LGraphShaderConstant()\r\n\t{\r\n\t\tthis.addOutput(\"\",\"float\");\r\n\r\n\t\tthis.properties = {\r\n\t\t\ttype: \"float\",\r\n\t\t\tvalue: 0\r\n\t\t};\r\n\r\n\t\tthis.addWidget(\"combo\",\"type\",\"float\",null, { values: GLSL_types_const, property: \"type\" } );\r\n\t\tthis.updateWidgets();\r\n\t}\r\n\r\n\tLGraphShaderConstant.title = \"const\";\r\n\r\n\tLGraphShaderConstant.prototype.getTitle = function()\r\n\t{\r\n\t\tif(this.flags.collapsed)\r\n\t\t\treturn valueToGLSL( this.properties.value, this.properties.type, 2 );\r\n\t\treturn \"Const\";\r\n\t}\r\n\r\n\tLGraphShaderConstant.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tif(name == \"type\")\r\n\t\t{\r\n\t\t\tif(this.outputs[0].type != value)\r\n\t\t\t{\r\n\t\t\t\tthis.disconnectOutput(0);\r\n\t\t\t\tthis.outputs[0].type = value;\r\n\t\t\t}\r\n\t\t\tthis.widgets.length = 1; //remove extra widgets\r\n\t\t\tthis.updateWidgets();\r\n\t\t}\r\n\t\tif(name == \"value\")\r\n\t\t{\r\n\t\t\tif(!value.length)\r\n\t\t\t\tthis.widgets[1].value = value;\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.widgets[1].value = value[1];\r\n\t\t\t\tif(value.length > 2)\r\n\t\t\t\t\tthis.widgets[2].value = value[2];\r\n\t\t\t\tif(value.length > 3)\r\n\t\t\t\t\tthis.widgets[3].value = value[3];\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderConstant.prototype.updateWidgets = function( old_value )\r\n\t{\r\n\t\tvar that = this;\r\n\t\tvar old_value = this.properties.value;\r\n\t\tvar options = { step: 0.01 };\r\n\t\tswitch(this.properties.type)\r\n\t\t{\r\n\t\t\tcase 'float': \r\n\t\t\t\tthis.properties.value = 0;\r\n\t\t\t\tthis.addWidget(\"number\",\"v\",0,{ step:0.01, property: \"value\" });\r\n\t\t\t\tbreak;\r\n\t\t\tcase 'vec2': \r\n\t\t\t\tthis.properties.value = old_value && old_value.length == 2 ? [old_value[0],old_value[1]] : [0,0,0];\r\n\t\t\t\tthis.addWidget(\"number\",\"x\",this.properties.value[0], function(v){ that.properties.value[0] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"y\",this.properties.value[1], function(v){ that.properties.value[1] = v; },options); \r\n\t\t\t\tbreak;\r\n\t\t\tcase 'vec3': \r\n\t\t\t\tthis.properties.value = old_value && old_value.length == 3 ? [old_value[0],old_value[1],old_value[2]] : [0,0,0];\r\n\t\t\t\tthis.addWidget(\"number\",\"x\",this.properties.value[0], function(v){ that.properties.value[0] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"y\",this.properties.value[1], function(v){ that.properties.value[1] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"z\",this.properties.value[2], function(v){ that.properties.value[2] = v; },options); \r\n\t\t\t\tbreak;\r\n\t\t\tcase 'vec4': \r\n\t\t\t\tthis.properties.value = old_value && old_value.length == 4 ? [old_value[0],old_value[1],old_value[2],old_value[3]] : [0,0,0,0];\r\n\t\t\t\tthis.addWidget(\"number\",\"x\",this.properties.value[0], function(v){ that.properties.value[0] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"y\",this.properties.value[1], function(v){ that.properties.value[1] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"z\",this.properties.value[2], function(v){ that.properties.value[2] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"w\",this.properties.value[3], function(v){ that.properties.value[3] = v; },options); \r\n\t\t\t\tbreak;\r\n\t\t\tdefault:\r\n\t\t\t\tconsole.error(\"unknown type for constant\");\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderConstant.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar value = valueToGLSL( this.properties.value, this.properties.type );\r\n\t\tvar link_name = getOutputLinkID(this,0);\r\n\t\tif(!link_name) //not connected\r\n\t\t\treturn;\r\n\r\n\t\tvar code = \"\t\" + this.properties.type + \" \" + link_name + \" = \" + value + \";\";\r\n\t\tcontext.addCode( \"code\", code, this.shader_destination );\r\n\r\n\t\tthis.setOutputData( 0, this.properties.type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"const/const\", LGraphShaderConstant );\r\n\r\n\tfunction LGraphShaderVec2()\r\n\t{\r\n\t\tthis.addInput(\"xy\",\"vec2\");\r\n\t\tthis.addInput(\"x\",\"float\");\r\n\t\tthis.addInput(\"y\",\"float\");\r\n\t\tthis.addOutput(\"xy\",\"vec2\");\r\n\t\tthis.addOutput(\"x\",\"float\");\r\n\t\tthis.addOutput(\"y\",\"float\");\r\n\r\n\t\tthis.properties = { x: 0, y: 0 };\r\n\t}\r\n\r\n\tLGraphShaderVec2.title = \"vec2\";\r\n\tLGraphShaderVec2.varmodes = [\"xy\",\"x\",\"y\"];\r\n\r\n\tLGraphShaderVec2.prototype.onPropertyChanged = function()\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\t this.graph._version++;\r\n\t}\r\n\r\n\tLGraphShaderVec2.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar props = this.properties;\r\n\r\n\t\tvar varname = getShaderNodeVarName(this);\r\n\t\tvar code = \"\tvec2 \" + varname + \" = \" + valueToGLSL([props.x,props.y]) + \";\\n\";\r\n\r\n\t\tfor(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec2.varmodes[i];\r\n\t\t\tvar inlink = getInputLinkID(this,i);\r\n\t\t\tif(!inlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tcode += \"\t\" + varname + \".\"+varmode+\" = \" + inlink + \";\\n\";\r\n\t\t}\r\n\r\n\t\tfor(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec2.varmodes[i];\r\n\t\t\tvar outlink = getOutputLinkID(this,i);\r\n\t\t\tif(!outlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar type = GLSL_types_const[varmode.length - 1];\r\n\t\t\tcode += \"\t\"+type+\" \" + outlink + \" = \" + varname + \".\" + varmode + \";\\n\";\r\n\t\t\tthis.setOutputData( i, type );\r\n\t\t}\r\n\r\n\t\tcontext.addCode( \"code\", code, this.shader_destination );\r\n\t}\r\n\r\n\tregisterShaderNode( \"const/vec2\", LGraphShaderVec2 );\t\r\n\r\n\tfunction LGraphShaderVec3()\r\n\t{\r\n\t\tthis.addInput(\"xyz\",\"vec3\");\r\n\t\tthis.addInput(\"x\",\"float\");\r\n\t\tthis.addInput(\"y\",\"float\");\r\n\t\tthis.addInput(\"z\",\"float\");\r\n\t\tthis.addInput(\"xy\",\"vec2\");\r\n\t\tthis.addInput(\"xz\",\"vec2\");\r\n\t\tthis.addInput(\"yz\",\"vec2\");\r\n\t\tthis.addOutput(\"xyz\",\"vec3\");\r\n\t\tthis.addOutput(\"x\",\"float\");\r\n\t\tthis.addOutput(\"y\",\"float\");\r\n\t\tthis.addOutput(\"z\",\"float\");\r\n\t\tthis.addOutput(\"xy\",\"vec2\");\r\n\t\tthis.addOutput(\"xz\",\"vec2\");\r\n\t\tthis.addOutput(\"yz\",\"vec2\");\r\n\r\n\t\tthis.properties = { x:0, y: 0, z: 0 };\r\n\t}\r\n\r\n\tLGraphShaderVec3.title = \"vec3\";\r\n\tLGraphShaderVec3.varmodes = [\"xyz\",\"x\",\"y\",\"z\",\"xy\",\"xz\",\"yz\"];\r\n\r\n\tLGraphShaderVec3.prototype.onPropertyChanged = function()\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\tthis.graph._version++;\r\n\t}\r\n\r\n\tLGraphShaderVec3.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar props = this.properties;\r\n\r\n\t\tvar varname = getShaderNodeVarName(this);\r\n\t\tvar code = \"vec3 \" + varname + \" = \" + valueToGLSL([props.x,props.y,props.z]) + \";\\n\";\r\n\r\n\t\tfor(var i = 0;i < LGraphShaderVec3.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec3.varmodes[i];\r\n\t\t\tvar inlink = getInputLinkID(this,i);\r\n\t\t\tif(!inlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tcode += \"\t\" + varname + \".\"+varmode+\" = \" + inlink + \";\\n\";\r\n\t\t}\r\n\r\n\t\tfor(var i = 0; i < LGraphShaderVec3.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec3.varmodes[i];\r\n\t\t\tvar outlink = getOutputLinkID(this,i);\r\n\t\t\tif(!outlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar type = GLSL_types_const[varmode.length - 1];\r\n\t\t\tcode += \"\t\"+type+\" \" + outlink + \" = \" + varname + \".\" + varmode + \";\\n\";\r\n\t\t\tthis.setOutputData( i, type );\r\n\t\t}\r\n\r\n\t\tcontext.addCode( \"code\", code, this.shader_destination );\r\n\t}\r\n\r\n\tregisterShaderNode( \"const/vec3\", LGraphShaderVec3 );\t\r\n\r\n\r\n\tfunction LGraphShaderVec4()\r\n\t{\r\n\t\tthis.addInput(\"xyzw\",\"vec4\");\r\n\t\tthis.addInput(\"xyz\",\"vec3\");\r\n\t\tthis.addInput(\"x\",\"float\");\r\n\t\tthis.addInput(\"y\",\"float\");\r\n\t\tthis.addInput(\"z\",\"float\");\r\n\t\tthis.addInput(\"w\",\"float\");\r\n\t\tthis.addInput(\"xy\",\"vec2\");\r\n\t\tthis.addInput(\"yz\",\"vec2\");\r\n\t\tthis.addInput(\"zw\",\"vec2\");\r\n\t\tthis.addOutput(\"xyzw\",\"vec4\");\r\n\t\tthis.addOutput(\"xyz\",\"vec3\");\r\n\t\tthis.addOutput(\"x\",\"float\");\r\n\t\tthis.addOutput(\"y\",\"float\");\r\n\t\tthis.addOutput(\"z\",\"float\");\r\n\t\tthis.addOutput(\"xy\",\"vec2\");\r\n\t\tthis.addOutput(\"yz\",\"vec2\");\r\n\t\tthis.addOutput(\"zw\",\"vec2\");\r\n\r\n\t\tthis.properties = { x:0, y: 0, z: 0, w: 0 };\r\n\t}\r\n\r\n\tLGraphShaderVec4.title = \"vec4\";\r\n\tLGraphShaderVec4.varmodes = [\"xyzw\",\"xyz\",\"x\",\"y\",\"z\",\"w\",\"xy\",\"yz\",\"zw\"];\r\n\r\n\tLGraphShaderVec4.prototype.onPropertyChanged = function()\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\tthis.graph._version++;\r\n\t}\r\n\r\n\tLGraphShaderVec4.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar props = this.properties;\r\n\r\n\t\tvar varname = getShaderNodeVarName(this);\r\n\t\tvar code = \"vec4 \" + varname + \" = \" + valueToGLSL([props.x,props.y,props.z,props.w]) + \";\\n\";\r\n\r\n\t\tfor(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec4.varmodes[i];\r\n\t\t\tvar inlink = getInputLinkID(this,i);\r\n\t\t\tif(!inlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tcode += \"\t\" + varname + \".\"+varmode+\" = \" + inlink + \";\\n\";\r\n\t\t}\r\n\r\n\t\tfor(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec4.varmodes[i];\r\n\t\t\tvar outlink = getOutputLinkID(this,i);\r\n\t\t\tif(!outlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar type = GLSL_types_const[varmode.length - 1];\r\n\t\t\tcode += \"\t\"+type+\" \" + outlink + \" = \" + varname + \".\" + varmode + \";\\n\";\r\n\t\t\tthis.setOutputData( i, type );\r\n\t\t}\r\n\r\n\t\tcontext.addCode( \"code\", code, this.shader_destination );\r\n\r\n\t}\r\n\r\n\tregisterShaderNode( \"const/vec4\", LGraphShaderVec4 );\t\r\n\t\r\n\t//*********************************\r\n\r\n\tfunction LGraphShaderFragColor() {\r\n\t\tthis.addInput(\"color\", LGShaders.ALL_TYPES );\r\n\t\tthis.block_delete = true;\r\n\t}\r\n\r\n\tLGraphShaderFragColor.title = \"FragColor\";\r\n\tLGraphShaderFragColor.desc = \"Pixel final color\";\r\n\r\n\tLGraphShaderFragColor.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tvar link_name = getInputLinkID( this, 0 );\r\n\t\tif(!link_name)\r\n\t\t\treturn;\r\n\t\tvar type = this.getInputData(0);\r\n\t\tvar code = varToTypeGLSL( link_name, type, \"vec4\" );\r\n\t\tcontext.addCode(\"fs_code\", \"fragcolor = \" + code + \";\");\r\n\t}\r\n\r\n\tregisterShaderNode( \"output/fragcolor\", LGraphShaderFragColor );\r\n\r\n\r\n\t/*\r\n\tfunction LGraphShaderDiscard()\r\n\t{\r\n\t\tthis.addInput(\"v\",\"T\");\r\n\t\tthis.addInput(\"min\",\"T\");\r\n\t\tthis.properties = { min_value: 0.0 };\r\n\t\tthis.addWidget(\"number\",\"min\",0,{ step: 0.01, property: \"min_value\" });\r\n\t}\r\n\r\n\tLGraphShaderDiscard.title = \"Discard\";\r\n\r\n\tLGraphShaderDiscard.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlink = getInputLinkID(this,0);\r\n\t\tvar inlink1 = getInputLinkID(this,1);\r\n\r\n\t\tif(!inlink && !inlink1) //not connected\r\n\t\t\treturn;\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = ( (\" + inlink + \" - \"+minv+\") / (\"+ maxv+\" - \"+minv+\") ) * (\"+ maxv2+\" - \"+minv2+\") + \" + minv2 + \";\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"output/discard\", LGraphShaderDiscard );\r\n\t*/\r\n\r\n\r\n\t// *************************************************\r\n\r\n\tfunction LGraphShaderOperation()\r\n\t{\r\n\t\tthis.addInput(\"A\", LGShaders.ALL_TYPES );\r\n\t\tthis.addInput(\"B\", LGShaders.ALL_TYPES );\r\n\t\tthis.addOutput(\"out\",\"\");\r\n\t\tthis.properties = {\r\n\t\t\toperation: \"*\"\r\n\t\t};\r\n\t\tthis.addWidget(\"combo\",\"op.\",this.properties.operation,{ property: \"operation\", values: LGraphShaderOperation.operations });\r\n\t}\r\n\r\n\tLGraphShaderOperation.title = \"Operation\";\r\n\tLGraphShaderOperation.operations = [\"+\",\"-\",\"*\",\"/\"];\r\n\r\n\tLGraphShaderOperation.prototype.getTitle = function()\r\n\t{\r\n\t\tif(this.flags.collapsed)\r\n\t\t\treturn \"A\" + this.properties.operation + \"B\";\r\n\t\telse\r\n\t\t\treturn \"Operation\";\r\n\t}\r\n\r\n\tLGraphShaderOperation.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tif(!this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlinks = [];\r\n\t\tfor(var i = 0; i < 3; ++i)\r\n\t\t\tinlinks.push( { name: getInputLinkID(this,i), type: this.getInputData(i) || \"float\" } );\r\n\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\t\tif(!outlink) //not connected\r\n\t\t\treturn;\r\n\r\n\t\t//func_desc\r\n\t\tvar base_type = inlinks[0].type;\r\n\t\tvar return_type = base_type;\r\n\t\tvar op = this.properties.operation;\r\n\r\n\t\tvar params = [];\r\n\t\tfor(var i = 0; i < 2; ++i)\r\n\t\t{\r\n\t\t\tvar param_code = inlinks[i].name;\r\n\t\t\tif(param_code == null) //not plugged\r\n\t\t\t{\r\n\t\t\t\tparam_code = p.value != null ? p.value : \"(1.0)\";\r\n\t\t\t\tinlinks[i].type = \"float\";\r\n\t\t\t}\r\n\r\n\t\t\t//convert\r\n\t\t\tif( inlinks[i].type != base_type ) \r\n\t\t\t{\r\n\t\t\t\tif( inlinks[i].type == \"float\" && (op == \"*\" || op == \"/\") )\r\n\t\t\t\t{\r\n\t\t\t\t\t//I find hard to create the opposite condition now, so I prefeer an else\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t\tparam_code = convertVarToGLSLType( param_code, inlinks[i].type, base_type );\r\n\t\t\t}\r\n\t\t\tparams.push( param_code );\r\n\t\t}\r\n\t\t\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = \"+ params[0] + op + params[1] + \";\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"math/operation\", LGraphShaderOperation );\r\n\r\n\r\n\tfunction LGraphShaderFunc()\r\n\t{\r\n\t\tthis.addInput(\"A\", LGShaders.ALL_TYPES );\r\n\t\tthis.addInput(\"B\", LGShaders.ALL_TYPES );\r\n\t\tthis.addOutput(\"out\",\"\");\r\n\t\tthis.properties = {\r\n\t\t\tfunc: \"floor\"\r\n\t\t};\r\n\t\tthis._current = \"floor\";\r\n\t\tthis.addWidget(\"combo\",\"func\",this.properties.func,{ property: \"func\", values: GLSL_functions_name });\r\n\t}\r\n\r\n\tLGraphShaderFunc.title = \"Func\";\r\n\r\n\tLGraphShaderFunc.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\tthis.graph._version++;\r\n\r\n\t\tif(name == \"func\")\r\n\t\t{\r\n\t\t\tvar func_desc = GLSL_functions[ value ];\r\n\t\t\tif(!func_desc)\r\n\t\t\t\treturn;\r\n\r\n\t\t\t//remove extra inputs\r\n\t\t\tfor(var i = func_desc.params.length; i < this.inputs.length; ++i)\r\n\t\t\t\tthis.removeInput(i);\r\n\r\n\t\t\t//add and update inputs\r\n\t\t\tfor(var i = 0; i < func_desc.params.length; ++i)\r\n\t\t\t{\r\n\t\t\t\tvar p = func_desc.params[i];\r\n\t\t\t\tif( this.inputs[i] )\r\n\t\t\t\t\tthis.inputs[i].name = p.name + (p.value ? \" (\" + p.value + \")\" : \"\");\r\n\t\t\t\telse\r\n\t\t\t\t\tthis.addInput( p.name, LGShaders.ALL_TYPES );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderFunc.prototype.getTitle = function()\r\n\t{\r\n\t\tif(this.flags.collapsed)\r\n\t\t\treturn this.properties.func;\r\n\t\telse\r\n\t\t\treturn \"Func\";\r\n\t}\r\n\r\n\tLGraphShaderFunc.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tif(!this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlinks = [];\r\n\t\tfor(var i = 0; i < 3; ++i)\r\n\t\t\tinlinks.push( { name: getInputLinkID(this,i), type: this.getInputData(i) || \"float\" } );\r\n\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\t\tif(!outlink) //not connected\r\n\t\t\treturn;\r\n\r\n\t\tvar func_desc = GLSL_functions[ this.properties.func ];\r\n\t\tif(!func_desc)\r\n\t\t\treturn;\r\n\r\n\t\t//func_desc\r\n\t\tvar base_type = inlinks[0].type;\r\n\t\tvar return_type = func_desc.return_type;\r\n\t\tif( return_type == \"T\" )\r\n\t\t\treturn_type = base_type;\r\n\r\n\t\tvar params = [];\r\n\t\tfor(var i = 0; i < func_desc.params.length; ++i)\r\n\t\t{\r\n\t\t\tvar p = func_desc.params[i];\r\n\t\t\tvar param_code = inlinks[i].name;\r\n\t\t\tif(param_code == null) //not plugged\r\n\t\t\t{\r\n\t\t\t\tparam_code = p.value != null ? p.value : \"(1.0)\";\r\n\t\t\t\tinlinks[i].type = \"float\";\r\n\t\t\t}\r\n\t\t\tif( (p.type == \"T\" && inlinks[i].type != base_type) ||\r\n\t\t\t\t(p.type != \"T\" && inlinks[i].type != base_type) )\r\n\t\t\t\tparam_code = convertVarToGLSLType( param_code, inlinks[i].type, base_type );\r\n\t\t\tparams.push( param_code );\r\n\t\t}\r\n\t\t\r\n\t\tcontext.addFunction(\"round\",\"float round(float v){ return floor(v+0.5); }\\nvec2 round(vec2 v){ return floor(v+vec2(0.5));}\\nvec3 round(vec3 v){ return floor(v+vec3(0.5));}\\nvec4 round(vec4 v){ return floor(v+vec4(0.5)); }\\n\");\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = \"+func_desc.func+\"(\"+params.join(\",\")+\");\", this.shader_destination );\r\n\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"math/func\", LGraphShaderFunc );\r\n\r\n\r\n\r\n\tfunction LGraphShaderSnippet()\r\n\t{\r\n\t\tthis.addInput(\"A\", LGShaders.ALL_TYPES );\r\n\t\tthis.addInput(\"B\", LGShaders.ALL_TYPES );\r\n\t\tthis.addOutput(\"C\",\"vec4\");\r\n\t\tthis.properties = {\r\n\t\t\tcode:\"C = A+B\",\r\n\t\t\ttype: \"vec4\"\r\n\t\t}\r\n\t\tthis.addWidget(\"text\",\"code\",this.properties.code,{ property: \"code\" });\r\n\t\tthis.addWidget(\"combo\",\"type\",this.properties.type,{ values:[\"float\",\"vec2\",\"vec3\",\"vec4\"], property: \"type\" });\r\n\t}\r\n\r\n\tLGraphShaderSnippet.title = \"Snippet\";\r\n\r\n\tLGraphShaderSnippet.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\tthis.graph._version++;\r\n\r\n\t\tif(name == \"type\"&& this.outputs[0].type != value)\r\n\t\t{\r\n\t\t\tthis.disconnectOutput(0);\r\n\t\t\tthis.outputs[0].type = value;\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderSnippet.prototype.getTitle = function()\r\n\t{\r\n\t\tif(this.flags.collapsed)\r\n\t\t\treturn this.properties.code;\r\n\t\telse\r\n\t\t\treturn \"Snippet\";\r\n\t}\r\n\r\n\tLGraphShaderSnippet.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlinkA = getInputLinkID(this,0);\r\n\t\tif(!inlinkA)\r\n\t\t\tinlinkA = \"1.0\";\r\n\t\tvar inlinkB = getInputLinkID(this,1);\r\n\t\tif(!inlinkB)\r\n\t\t\tinlinkB = \"1.0\";\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\t\tif(!outlink) //not connected\r\n\t\t\treturn;\r\n\r\n\t\tvar inA_type = this.getInputData(0) || \"float\";\r\n\t\tvar inB_type = this.getInputData(1) || \"float\";\r\n\t\tvar return_type = this.properties.type;\r\n\r\n\t\t//cannot resolve input\r\n\t\tif(inA_type == \"T\" || inB_type == \"T\")\r\n\t\t{\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tvar funcname = \"funcSnippet\" + this.id;\r\n\r\n\t\tvar func_code = \"\\n\" + return_type + \" \" + funcname + \"( \" + inA_type + \" A, \" + inB_type + \" B) {\\n\";\r\n\t\tfunc_code += \"\t\" + return_type + \" C = \" + return_type + \"(0.0);\\n\";\r\n\t\tfunc_code += \"\t\" + this.properties.code + \";\\n\";\r\n\t\tfunc_code += \"\treturn C;\\n}\\n\";\r\n\r\n\t\tcontext.addCode(\"functions\", func_code, this.shader_destination );\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = \"+funcname+\"(\"+inlinkA+\",\"+inlinkB+\");\", this.shader_destination );\r\n\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"utils/snippet\", LGraphShaderSnippet );\r\n\r\n\t//************************************\r\n\r\n\tfunction LGraphShaderRand()\r\n\t{\r\n\t\tthis.addOutput(\"out\",\"float\");\r\n\t}\r\n\r\n\tLGraphShaderRand.title = \"Rand\";\r\n\r\n\tLGraphShaderRand.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\r\n\t\tcontext.addUniform( \"u_rand\" + this.id, \"float\", function(){ return Math.random(); });\r\n\t\tcontext.addCode(\"code\", \"float \" + outlink + \" = u_rand\" + this.id +\";\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, \"float\" );\r\n\t}\r\n\r\n\tregisterShaderNode( \"input/rand\", LGraphShaderRand );\r\n\r\n\t//noise\r\n\t//https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83\r\n\tfunction LGraphShaderNoise()\r\n\t{\r\n\t\tthis.addInput(\"out\", LGShaders.ALL_TYPES );\r\n\t\tthis.addInput(\"scale\", \"float\" );\r\n\t\tthis.addOutput(\"out\",\"float\");\r\n\t\tthis.properties = {\r\n\t\t\ttype: \"noise\",\r\n\t\t\tscale: 1\r\n\t\t};\r\n\t\tthis.addWidget(\"combo\",\"type\", this.properties.type, { property: \"type\", values: LGraphShaderNoise.NOISE_TYPES });\r\n\t\tthis.addWidget(\"number\",\"scale\", this.properties.scale, { property: \"scale\" });\r\n\t}\r\n\r\n\tLGraphShaderNoise.NOISE_TYPES = [\"noise\",\"rand\"];\r\n\r\n\tLGraphShaderNoise.title = \"noise\";\r\n\r\n\tLGraphShaderNoise.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlink = getInputLinkID(this,0);\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\r\n\t\tvar intype = this.getInputData(0);\r\n\t\tif(!inlink)\r\n\t\t{\r\n\t\t\tintype = \"vec2\";\r\n\t\t\tinlink = context.buffer_names.uvs;\r\n\t\t}\r\n\r\n\t\tcontext.addFunction(\"noise\",LGraphShaderNoise.shader_functions);\r\n\t\tcontext.addUniform( \"u_noise_scale\" + this.id, \"float\", this.properties.scale );\r\n\t\tif( intype == \"float\" )\r\n\t\t\tcontext.addCode(\"code\", \"float \" + outlink + \" = snoise( vec2(\" + inlink +\") * u_noise_scale\" + this.id +\");\", this.shader_destination );\r\n\t\telse if( intype == \"vec2\" || intype == \"vec3\" )\r\n\t\t\tcontext.addCode(\"code\", \"float \" + outlink + \" = snoise(\" + inlink +\" * u_noise_scale\" + this.id +\");\", this.shader_destination );\r\n\t\telse if( intype == \"vec4\" )\r\n\t\t\tcontext.addCode(\"code\", \"float \" + outlink + \" = snoise(\" + inlink +\".xyz * u_noise_scale\" + this.id +\");\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, \"float\" );\r\n\t}\r\n\r\n\tregisterShaderNode( \"math/noise\", LGraphShaderNoise );\r\n\r\nLGraphShaderNoise.shader_functions = \"\\n\\\r\nvec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }\\n\\\r\n\\n\\\r\nfloat snoise(vec2 v){\\n\\\r\n  const vec4 C = vec4(0.211324865405187, 0.366025403784439,-0.577350269189626, 0.024390243902439);\\n\\\r\n  vec2 i  = floor(v + dot(v, C.yy) );\\n\\\r\n  vec2 x0 = v -   i + dot(i, C.xx);\\n\\\r\n  vec2 i1;\\n\\\r\n  i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\\n\\\r\n  vec4 x12 = x0.xyxy + C.xxzz;\\n\\\r\n  x12.xy -= i1;\\n\\\r\n  i = mod(i, 289.0);\\n\\\r\n  vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))\\n\\\r\n  + i.x + vec3(0.0, i1.x, 1.0 ));\\n\\\r\n  vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),dot(x12.zw,x12.zw)), 0.0);\\n\\\r\n  m = m*m ;\\n\\\r\n  m = m*m ;\\n\\\r\n  vec3 x = 2.0 * fract(p * C.www) - 1.0;\\n\\\r\n  vec3 h = abs(x) - 0.5;\\n\\\r\n  vec3 ox = floor(x + 0.5);\\n\\\r\n  vec3 a0 = x - ox;\\n\\\r\n  m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );\\n\\\r\n  vec3 g;\\n\\\r\n  g.x  = a0.x  * x0.x  + h.x  * x0.y;\\n\\\r\n  g.yz = a0.yz * x12.xz + h.yz * x12.yw;\\n\\\r\n  return 130.0 * dot(m, g);\\n\\\r\n}\\n\\\r\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\\n\\\r\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\\n\\\r\n\\n\\\r\nfloat snoise(vec3 v){ \\n\\\r\n  const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;\\n\\\r\n  const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);\\n\\\r\n\\n\\\r\n// First corner\\n\\\r\n  vec3 i  = floor(v + dot(v, C.yyy) );\\n\\\r\n  vec3 x0 =   v - i + dot(i, C.xxx) ;\\n\\\r\n\\n\\\r\n// Other corners\\n\\\r\n  vec3 g = step(x0.yzx, x0.xyz);\\n\\\r\n  vec3 l = 1.0 - g;\\n\\\r\n  vec3 i1 = min( g.xyz, l.zxy );\\n\\\r\n  vec3 i2 = max( g.xyz, l.zxy );\\n\\\r\n\\n\\\r\n  //  x0 = x0 - 0. + 0.0 * C \\n\\\r\n  vec3 x1 = x0 - i1 + 1.0 * C.xxx;\\n\\\r\n  vec3 x2 = x0 - i2 + 2.0 * C.xxx;\\n\\\r\n  vec3 x3 = x0 - 1. + 3.0 * C.xxx;\\n\\\r\n\\n\\\r\n// Permutations\\n\\\r\n  i = mod(i, 289.0 ); \\n\\\r\n  vec4 p = permute( permute( permute( \\n\\\r\n             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))\\n\\\r\n           + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) \\n\\\r\n           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));\\n\\\r\n\\n\\\r\n// Gradients\\n\\\r\n// ( N*N points uniformly over a square, mapped onto an octahedron.)\\n\\\r\n  float n_ = 1.0/7.0; // N=7\\n\\\r\n  vec3  ns = n_ * D.wyz - D.xzx;\\n\\\r\n\\n\\\r\n  vec4 j = p - 49.0 * floor(p * ns.z *ns.z);  //  mod(p,N*N)\\n\\\r\n\\n\\\r\n  vec4 x_ = floor(j * ns.z);\\n\\\r\n  vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)\\n\\\r\n\\n\\\r\n  vec4 x = x_ *ns.x + ns.yyyy;\\n\\\r\n  vec4 y = y_ *ns.x + ns.yyyy;\\n\\\r\n  vec4 h = 1.0 - abs(x) - abs(y);\\n\\\r\n\\n\\\r\n  vec4 b0 = vec4( x.xy, y.xy );\\n\\\r\n  vec4 b1 = vec4( x.zw, y.zw );\\n\\\r\n\\n\\\r\n  vec4 s0 = floor(b0)*2.0 + 1.0;\\n\\\r\n  vec4 s1 = floor(b1)*2.0 + 1.0;\\n\\\r\n  vec4 sh = -step(h, vec4(0.0));\\n\\\r\n\\n\\\r\n  vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;\\n\\\r\n  vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;\\n\\\r\n\\n\\\r\n  vec3 p0 = vec3(a0.xy,h.x);\\n\\\r\n  vec3 p1 = vec3(a0.zw,h.y);\\n\\\r\n  vec3 p2 = vec3(a1.xy,h.z);\\n\\\r\n  vec3 p3 = vec3(a1.zw,h.w);\\n\\\r\n\\n\\\r\n//Normalise gradients\\n\\\r\n  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));\\n\\\r\n  p0 *= norm.x;\\n\\\r\n  p1 *= norm.y;\\n\\\r\n  p2 *= norm.z;\\n\\\r\n  p3 *= norm.w;\\n\\\r\n\\n\\\r\n// Mix final noise value\\n\\\r\n  vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);\\n\\\r\n  m = m * m;\\n\\\r\n  return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),dot(p2,x2), dot(p3,x3) ) );\\n\\\r\n}\\n\\\r\n\\n\\\r\nvec3 hash3( vec2 p ){\\n\\\r\n    vec3 q = vec3( dot(p,vec2(127.1,311.7)), \\n\\\r\n\t\t\t\t   dot(p,vec2(269.5,183.3)), \\n\\\r\n\t\t\t\t   dot(p,vec2(419.2,371.9)) );\\n\\\r\n\treturn fract(sin(q)*43758.5453);\\n\\\r\n}\\n\\\r\nvec4 hash4( vec3 p ){\\n\\\r\n    vec4 q = vec4( dot(p,vec3(127.1,311.7,257.3)), \\n\\\r\n\t\t\t\t   dot(p,vec3(269.5,183.3,335.1)), \\n\\\r\n\t\t\t\t   dot(p,vec3(314.5,235.1,467.3)), \\n\\\r\n\t\t\t\t   dot(p,vec3(419.2,371.9,114.9)) );\\n\\\r\n\treturn fract(sin(q)*43758.5453);\\n\\\r\n}\\n\\\r\n\\n\\\r\nfloat iqnoise( in vec2 x, float u, float v ){\\n\\\r\n    vec2 p = floor(x);\\n\\\r\n    vec2 f = fract(x);\\n\\\r\n\t\\n\\\r\n\tfloat k = 1.0+63.0*pow(1.0-v,4.0);\\n\\\r\n\t\\n\\\r\n\tfloat va = 0.0;\\n\\\r\n\tfloat wt = 0.0;\\n\\\r\n    for( int j=-2; j<=2; j++ )\\n\\\r\n    for( int i=-2; i<=2; i++ )\\n\\\r\n    {\\n\\\r\n        vec2 g = vec2( float(i),float(j) );\\n\\\r\n\t\tvec3 o = hash3( p + g )*vec3(u,u,1.0);\\n\\\r\n\t\tvec2 r = g - f + o.xy;\\n\\\r\n\t\tfloat d = dot(r,r);\\n\\\r\n\t\tfloat ww = pow( 1.0-smoothstep(0.0,1.414,sqrt(d)), k );\\n\\\r\n\t\tva += o.z*ww;\\n\\\r\n\t\twt += ww;\\n\\\r\n    }\\n\\\r\n\t\\n\\\r\n    return va/wt;\\n\\\r\n}\\n\\\r\n\"\r\n\r\n\tfunction LGraphShaderTime()\r\n\t{\r\n\t\tthis.addOutput(\"out\",\"float\");\r\n\t}\r\n\r\n\tLGraphShaderTime.title = \"Time\";\r\n\r\n\tLGraphShaderTime.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\r\n\t\tcontext.addUniform( \"u_time\" + this.id, \"float\", function(){ return getTime() * 0.001; });\r\n\t\tcontext.addCode(\"code\", \"float \" + outlink + \" = u_time\" + this.id +\";\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, \"float\" );\r\n\t}\r\n\r\n\tregisterShaderNode( \"input/time\", LGraphShaderTime );\r\n\r\n\r\n\tfunction LGraphShaderDither()\r\n\t{\r\n\t\tthis.addInput(\"in\",\"T\");\r\n\t\tthis.addOutput(\"out\",\"float\");\r\n\t}\r\n\r\n\tLGraphShaderDither.title = \"Dither\";\r\n\r\n\tLGraphShaderDither.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlink = getInputLinkID(this,0);\r\n\t\tvar return_type = \"float\";\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\t\tvar intype = this.getInputData(0);\r\n\t\tinlink = varToTypeGLSL( inlink, intype, \"float\" );\r\n\t\tcontext.addFunction(\"dither8x8\", LGraphShaderDither.dither_func);\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = dither8x8(\"+ inlink +\");\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tLGraphShaderDither.dither_values = [0.515625,0.140625,0.640625,0.046875,0.546875,0.171875,0.671875,0.765625,0.265625,0.890625,0.390625,0.796875,0.296875,0.921875,0.421875,0.203125,0.703125,0.078125,0.578125,0.234375,0.734375,0.109375,0.609375,0.953125,0.453125,0.828125,0.328125,0.984375,0.484375,0.859375,0.359375,0.0625,0.5625,0.1875,0.6875,0.03125,0.53125,0.15625,0.65625,0.8125,0.3125,0.9375,0.4375,0.78125,0.28125,0.90625,0.40625,0.25,0.75,0.125,0.625,0.21875,0.71875,0.09375,0.59375,1.0001,0.5,0.875,0.375,0.96875,0.46875,0.84375,0.34375];\r\n\t\r\n\tLGraphShaderDither.dither_func = \"\\n\\\r\n\t\tfloat dither8x8(float brightness) {\\n\\\r\n\t\t  vec2 position = vec2(0.0);\\n\\\r\n\t\t  #ifdef FRAGMENT\\n\\\r\n\t\t\tposition = gl_FragCoord.xy;\\n\\\r\n\t\t  #endif\\n\\\r\n\t\t  int x = int(mod(position.x, 8.0));\\n\\\r\n\t\t  int y = int(mod(position.y, 8.0));\\n\\\r\n\t\t  int index = x + y * 8;\\n\\\r\n\t\t  float limit = 0.0;\\n\\\r\n\t\t  if (x < 8) {\\n\\\r\n\t\t\tif(index==0) limit = 0.015625;\\n\\\r\n\t\t\t\"+(LGraphShaderDither.dither_values.map( function(v,i){ return \"else if(index== \"+(i+1)+\") limit = \" + v + \";\"}).join(\"\\n\"))+\"\\n\\\r\n\t\t  }\\n\\\r\n\t\t  return brightness < limit ? 0.0 : 1.0;\\n\\\r\n\t\t}\\n\",\r\n\r\n\tregisterShaderNode( \"math/dither\", LGraphShaderDither );\r\n\r\n\tfunction LGraphShaderRemap()\r\n\t{\r\n\t\tthis.addInput(\"\", LGShaders.ALL_TYPES );\r\n\t\tthis.addOutput(\"\",\"\");\r\n\t\tthis.properties = {\r\n\t\t\tmin_value: 0,\r\n\t\t\tmax_value: 1,\r\n\t\t\tmin_value2: 0,\r\n\t\t\tmax_value2: 1\r\n\t\t};\r\n\t\tthis.addWidget(\"number\",\"min\",0,{ step: 0.1, property: \"min_value\" });\r\n\t\tthis.addWidget(\"number\",\"max\",1,{ step: 0.1, property: \"max_value\" });\r\n\t\tthis.addWidget(\"number\",\"min2\",0,{ step: 0.1, property: \"min_value2\"});\r\n\t\tthis.addWidget(\"number\",\"max2\",1,{ step: 0.1, property: \"max_value2\"});\r\n\t}\r\n\r\n\tLGraphShaderRemap.title = \"Remap\";\r\n\r\n\tLGraphShaderRemap.prototype.onPropertyChanged = function()\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\tthis.graph._version++;\r\n\t}\r\n\r\n\tLGraphShaderRemap.prototype.onConnectionsChange = function()\r\n\t{\r\n\t\tvar return_type = this.getInputDataType(0);\r\n\t\tthis.outputs[0].type = return_type || \"T\";\r\n\t}\r\n\r\n\tLGraphShaderRemap.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlink = getInputLinkID(this,0);\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\t\tif(!inlink && !outlink) //not connected\r\n\t\t\treturn;\r\n\r\n\t\tvar return_type = this.getInputDataType(0);\r\n\t\tthis.outputs[0].type = return_type;\r\n\t\tif(return_type == \"T\")\r\n\t\t{\r\n\t\t\tconsole.warn(\"node type is T and cannot be resolved\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif(!inlink)\r\n\t\t{\r\n\t\t\tcontext.addCode(\"code\",\"\t\" + return_type + \" \" + outlink + \" = \" + return_type + \"(0.0);\\n\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar minv = valueToGLSL( this.properties.min_value );\r\n\t\tvar maxv = valueToGLSL( this.properties.max_value );\r\n\t\tvar minv2 = valueToGLSL( this.properties.min_value2 );\r\n\t\tvar maxv2 = valueToGLSL( this.properties.max_value2 );\r\n\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = ( (\" + inlink + \" - \"+minv+\") / (\"+ maxv+\" - \"+minv+\") ) * (\"+ maxv2+\" - \"+minv2+\") + \" + minv2 + \";\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"math/remap\", LGraphShaderRemap );\r\n\r\n})(this);\r\n\r\n\r\n\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n\tvar view_matrix = new Float32Array(16);\r\n\tvar projection_matrix = new Float32Array(16);\r\n\tvar viewprojection_matrix = new Float32Array(16);\r\n\tvar model_matrix = new Float32Array(16);\r\n\tvar global_uniforms = {\r\n\t\tu_view: view_matrix,\r\n\t\tu_projection: projection_matrix,\r\n\t\tu_viewprojection: viewprojection_matrix,\r\n\t\tu_model: model_matrix \r\n\t};\r\n\r\n\tLiteGraph.LGraphRender = {\r\n\t\tonRequestCameraMatrices: null //overwrite with your 3D engine specifics, it will receive (view_matrix, projection_matrix,viewprojection_matrix) and must be filled\r\n\t};\r\n\r\n\tfunction generateGeometryId() {\r\n\t\treturn (Math.random() * 100000)|0;\r\n\t}\r\n\r\n\tfunction LGraphPoints3D() {\r\n\r\n\t\tthis.addInput(\"obj\", \"\");\r\n\t\tthis.addInput(\"radius\", \"number\");\r\n\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\t\tthis.addOutput(\"points\", \"[vec3]\");\r\n\t\tthis.properties = {\r\n\t\t\tradius: 1,\r\n\t\t\tnum_points: 4096,\r\n\t\t\tgenerate_normals: true,\r\n\t\t\tregular: false,\r\n\t\t\tmode: LGraphPoints3D.SPHERE,\r\n\t\t\tforce_update: false\r\n\t\t};\r\n\r\n\t\tthis.points = new Float32Array( this.properties.num_points * 3 );\r\n\t\tthis.normals = new Float32Array( this.properties.num_points * 3 );\r\n\t\tthis.must_update = true;\r\n\t\tthis.version = 0;\r\n\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"update\",null, function(){ that.must_update = true; });\r\n\r\n\t\tthis.geometry = {\r\n\t\t\tvertices: null,\r\n\t\t\t_id: generateGeometryId()\r\n\t\t}\r\n\r\n\t\tthis._old_obj = null;\r\n\t\tthis._last_radius = null;\r\n\t}\r\n\r\n\tglobal.LGraphPoints3D = LGraphPoints3D;\r\n\r\n\tLGraphPoints3D.RECTANGLE = 1;\r\n\tLGraphPoints3D.CIRCLE = 2;\r\n\r\n\tLGraphPoints3D.CUBE = 10;\r\n\tLGraphPoints3D.SPHERE = 11;\r\n\tLGraphPoints3D.HEMISPHERE = 12;\r\n\tLGraphPoints3D.INSIDE_SPHERE = 13;\r\n\r\n\tLGraphPoints3D.OBJECT = 20;\r\n\tLGraphPoints3D.OBJECT_UNIFORMLY = 21;\r\n\tLGraphPoints3D.OBJECT_INSIDE = 22;\r\n\r\n\tLGraphPoints3D.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 };\r\n\r\n\tLGraphPoints3D.widgets_info = {\r\n\t\tmode: { widget: \"combo\", values: LGraphPoints3D.MODE_VALUES }\r\n\t};\r\n\r\n\tLGraphPoints3D.title = \"list of points\";\r\n\tLGraphPoints3D.desc = \"returns an array of points\";\r\n\r\n\tLGraphPoints3D.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tthis.must_update = true;\r\n\t}\r\n\r\n\tLGraphPoints3D.prototype.onExecute = function() {\r\n\r\n\t\tvar obj = this.getInputData(0);\r\n\t\tif( obj != this._old_obj || (obj && obj._version != this._old_obj_version) )\r\n\t\t{\r\n\t\t\tthis._old_obj = obj;\r\n\t\t\tthis.must_update = true;\r\n\t\t}\r\n\r\n\t\tvar radius = this.getInputData(1);\r\n\t\tif(radius == null)\r\n\t\t\tradius = this.properties.radius;\r\n\t\tif( this._last_radius != radius )\r\n\t\t{\r\n\t\t\tthis._last_radius = radius;\r\n\t\t\tthis.must_update = true;\r\n\t\t}\r\n\r\n\t\tif(this.must_update || this.properties.force_update )\r\n\t\t{\r\n\t\t\tthis.must_update = false;\r\n\t\t\tthis.updatePoints();\r\n\t\t}\r\n\r\n\t\tthis.geometry.vertices = this.points;\r\n\t\tthis.geometry.normals = this.normals;\r\n\t\tthis.geometry._version = this.version;\r\n\r\n\t\tthis.setOutputData( 0, this.geometry );\r\n\t}\r\n\r\n\tLGraphPoints3D.prototype.updatePoints = function() {\r\n\t\tvar num_points = this.properties.num_points|0;\r\n\t\tif(num_points < 1)\r\n\t\t\tnum_points = 1;\r\n\r\n\t\tif(!this.points || this.points.length != num_points * 3)\r\n\t\t\tthis.points = new Float32Array( num_points * 3 );\r\n\r\n\t\tif(this.properties.generate_normals)\r\n\t\t{\r\n\t\t\tif (!this.normals || this.normals.length != this.points.length)\r\n\t\t\t\tthis.normals = new Float32Array( this.points.length );\r\n\t\t}\r\n\t\telse\r\n\t\t\tthis.normals = null;\r\n\r\n\t\tvar radius = this._last_radius || this.properties.radius;\r\n\t\tvar mode = this.properties.mode;\r\n\r\n\t\tvar obj = this.getInputData(0);\r\n\t\tthis._old_obj_version = obj ? obj._version : null;\r\n\r\n\t\tthis.points = LGraphPoints3D.generatePoints( radius, num_points, mode, this.points, this.normals, this.properties.regular, obj );\r\n\r\n\t\tthis.version++;\r\n\t}\r\n\r\n\t//global\r\n\tLGraphPoints3D.generatePoints = function( radius, num_points, mode, points, normals, regular, obj )\r\n\t{\r\n\t\tvar size = num_points * 3;\r\n\t\tif(!points || points.length != size)\r\n\t\t\tpoints = new Float32Array( size );\r\n\t\tvar temp = new Float32Array(3);\r\n\t\tvar UP = new Float32Array([0,1,0]);\r\n\r\n\t\tif(regular)\r\n\t\t{\r\n\t\t\tif( mode == LGraphPoints3D.RECTANGLE)\r\n\t\t\t{\r\n\t\t\t\tvar side = Math.floor(Math.sqrt(num_points));\r\n\t\t\t\tfor(var i = 0; i < side; ++i)\r\n\t\t\t\tfor(var j = 0; j < side; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar pos = i*3 + j*3*side;\r\n\t\t\t\t\tpoints[pos] = ((i/side) - 0.5) * radius * 2;\r\n\t\t\t\t\tpoints[pos+1] = 0;\r\n\t\t\t\t\tpoints[pos+2] = ((j/side) - 0.5) * radius * 2;\r\n\t\t\t\t}\r\n\t\t\t\tpoints = new Float32Array( points.subarray(0,side*side*3) );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t\t\t\t\tnormals.set(UP, i);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.SPHERE)\r\n\t\t\t{\r\n\t\t\t\tvar side = Math.floor(Math.sqrt(num_points));\r\n\t\t\t\tfor(var i = 0; i < side; ++i)\r\n\t\t\t\tfor(var j = 0; j < side; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar pos = i*3 + j*3*side;\r\n\t\t\t\t\tpolarToCartesian( temp, (i/side) * 2 * Math.PI, ((j/side) - 0.5) * 2 * Math.PI, radius );\r\n\t\t\t\t\tpoints[pos] = temp[0];\r\n\t\t\t\t\tpoints[pos+1] = temp[1];\r\n\t\t\t\t\tpoints[pos+2] = temp[2];\r\n\t\t\t\t}\r\n\t\t\t\tpoints = new Float32Array( points.subarray(0,side*side*3) );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.CIRCLE)\r\n\t\t\t{\r\n\t\t\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar angle = 2 * Math.PI * (i/size);\r\n\t\t\t\t\tpoints[i] = Math.cos( angle ) * radius;\r\n\t\t\t\t\tpoints[i+1] = 0;\r\n\t\t\t\t\tpoints[i+2] = Math.sin( angle ) * radius;\r\n\t\t\t\t}\r\n\t\t\t\tif(normals)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t\t\t\t\tnormals.set(UP, i);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse //non regular\r\n\t\t{\r\n\t\t\tif( mode == LGraphPoints3D.RECTANGLE)\r\n\t\t\t{\r\n\t\t\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t\t\t{\r\n\t\t\t\t\tpoints[i] = (Math.random() - 0.5) * radius * 2;\r\n\t\t\t\t\tpoints[i+1] = 0;\r\n\t\t\t\t\tpoints[i+2] = (Math.random() - 0.5) * radius * 2;\r\n\t\t\t\t}\r\n\t\t\t\tif(normals)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t\t\t\t\tnormals.set(UP, i);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.CUBE)\r\n\t\t\t{\r\n\t\t\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t\t\t{\r\n\t\t\t\t\tpoints[i] = (Math.random() - 0.5) * radius * 2;\r\n\t\t\t\t\tpoints[i+1] = (Math.random() - 0.5) * radius * 2;\r\n\t\t\t\t\tpoints[i+2] = (Math.random() - 0.5) * radius * 2;\r\n\t\t\t\t}\r\n\t\t\t\tif(normals)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t\t\t\t\tnormals.set(UP, i);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.SPHERE)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateSphere( points, size, radius );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.HEMISPHERE)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateHemisphere( points, size, radius );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.CIRCLE)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateInsideCircle( points, size, radius );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.INSIDE_SPHERE)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateInsideSphere( points, size, radius );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.OBJECT)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateFromObject( points, normals, size, obj, false );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.OBJECT_UNIFORMLY)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateFromObject( points, normals, size, obj, true );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.OBJECT_INSIDE)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateFromInsideObject( points, size, obj );\r\n\t\t\t\t//if(normals)\r\n\t\t\t\t//\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tconsole.warn(\"wrong mode in LGraphPoints3D\");\r\n\t\t}\r\n\r\n\t\treturn points;\r\n\t}\r\n\r\n\tLGraphPoints3D.generateSphericalNormals = function(points, normals)\r\n\t{\r\n\t\tvar temp = new Float32Array(3);\r\n\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t{\r\n\t\t\ttemp[0] = points[i];\r\n\t\t\ttemp[1] = points[i+1];\r\n\t\t\ttemp[2] = points[i+2];\r\n\t\t\tvec3.normalize(temp,temp);\r\n\t\t\tnormals.set(temp,i);\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphPoints3D.generateSphere = function (points, size, radius)\r\n\t{\r\n\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t{\r\n\t\t\tvar r1 = Math.random();\r\n\t\t\tvar r2 = Math.random();\r\n\t\t\tvar x = 2 * Math.cos( 2 * Math.PI * r1 ) * Math.sqrt( r2 * (1-r2) );\r\n\t\t\tvar y = 1 - 2 * r2;\r\n\t\t\tvar z = 2 * Math.sin( 2 * Math.PI * r1 ) * Math.sqrt( r2 * (1-r2) );\r\n\t\t\tpoints[i] = x * radius;\r\n\t\t\tpoints[i+1] = y * radius;\r\n\t\t\tpoints[i+2] = z * radius;\r\n\t\t}\t\t\t\r\n\t}\r\n\r\n\tLGraphPoints3D.generateHemisphere = function (points, size, radius)\r\n\t{\r\n\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t{\r\n\t\t\tvar r1 = Math.random();\r\n\t\t\tvar r2 = Math.random();\r\n\t\t\tvar x = Math.cos( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );\r\n\t\t\tvar y = r2;\r\n\t\t\tvar z = Math.sin( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );\r\n\t\t\tpoints[i] = x * radius;\r\n\t\t\tpoints[i+1] = y * radius;\r\n\t\t\tpoints[i+2] = z * radius;\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphPoints3D.generateInsideCircle = function (points, size, radius)\r\n\t{\r\n\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t{\r\n\t\t\tvar r1 = Math.random();\r\n\t\t\tvar r2 = Math.random();\r\n\t\t\tvar x = Math.cos( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );\r\n\t\t\tvar y = r2;\r\n\t\t\tvar z = Math.sin( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );\r\n\t\t\tpoints[i] = x * radius;\r\n\t\t\tpoints[i+1] = 0;\r\n\t\t\tpoints[i+2] = z * radius;\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphPoints3D.generateInsideSphere = function (points, size, radius)\r\n\t{\r\n\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t{\r\n\t\t\tvar u = Math.random();\r\n\t\t\tvar v = Math.random();\r\n\t\t\tvar theta = u * 2.0 * Math.PI;\r\n\t\t\tvar phi = Math.acos(2.0 * v - 1.0);\r\n\t\t\tvar r = Math.cbrt(Math.random()) * radius;\r\n\t\t\tvar sinTheta = Math.sin(theta);\r\n\t\t\tvar cosTheta = Math.cos(theta);\r\n\t\t\tvar sinPhi = Math.sin(phi);\r\n\t\t\tvar cosPhi = Math.cos(phi);\r\n\t\t\tpoints[i] = r * sinPhi * cosTheta;\r\n\t\t\tpoints[i+1] = r * sinPhi * sinTheta;\r\n\t\t\tpoints[i+2] = r * cosPhi;\r\n\t\t}\t\r\n\t}\r\n\r\n\tfunction findRandomTriangle( areas, f )\r\n\t{\r\n\t\tvar l = areas.length;\r\n\t\tvar imin = 0;\r\n\t\tvar imid = 0;\r\n\t\tvar imax = l;\r\n\r\n\t\tif(l == 0)\r\n\t\t\treturn -1;\r\n\t\tif(l == 1)\r\n\t\t\treturn 0;\r\n\t\t//dichotomic search\r\n\t\twhile (imax >= imin)\r\n\t\t{\r\n\t\t\timid = ((imax + imin)*0.5)|0;\r\n\t\t\tvar t = areas[ imid ];\r\n\t\t\tif( t == f )\r\n\t\t\t\treturn imid; \r\n\t\t\tif( imin == (imax - 1) )\r\n\t\t\t\treturn imin;\r\n\t\t\tif (t < f)\r\n\t\t\t\timin = imid;\r\n\t\t\telse         \r\n\t\t\t\timax = imid;\r\n\t\t}\r\n\t\treturn imid;\t\t\r\n\t}\r\n\r\n\tLGraphPoints3D.generateFromObject = function( points, normals, size, obj, evenly )\r\n\t{\r\n\t\tif(!obj)\r\n\t\t\treturn;\r\n\r\n\t\tvar vertices = null;\r\n\t\tvar mesh_normals = null;\r\n\t\tvar indices = null;\r\n\t\tvar areas = null;\r\n\t\tif( obj.constructor === GL.Mesh )\r\n\t\t{\r\n\t\t\tvertices = obj.vertexBuffers.vertices.data;\r\n\t\t\tmesh_normals = obj.vertexBuffers.normals ? obj.vertexBuffers.normals.data : null;\r\n\t\t\tindices = obj.indexBuffers.indices ? obj.indexBuffers.indices.data : null;\r\n\t\t\tif(!indices)\r\n\t\t\t\tindices = obj.indexBuffers.triangles ? obj.indexBuffers.triangles.data : null;\r\n\t\t}\r\n\t\tif(!vertices)\r\n\t\t\treturn null;\r\n\t\tvar num_triangles = indices ? indices.length / 3 : vertices.length / (3*3);\r\n\t\tvar total_area = 0; //sum of areas of all triangles\r\n\r\n\t\tif(evenly)\r\n\t\t{\r\n\t\t\tareas = new Float32Array(num_triangles); //accum\r\n\t\t\tfor(var i = 0; i < num_triangles; ++i)\r\n\t\t\t{\r\n\t\t\t\tif(indices)\r\n\t\t\t\t{\r\n\t\t\t\t\ta = indices[i*3]*3;\r\n\t\t\t\t\tb = indices[i*3+1]*3;\r\n\t\t\t\t\tc = indices[i*3+2]*3;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\ta = i*9;\r\n\t\t\t\t\tb = i*9+3;\r\n\t\t\t\t\tc = i*9+6;\r\n\t\t\t\t}\r\n\t\t\t\tvar P1 = vertices.subarray(a,a+3);\r\n\t\t\t\tvar P2 = vertices.subarray(b,b+3);\r\n\t\t\t\tvar P3 = vertices.subarray(c,c+3);\r\n\t\t\t\tvar aL = vec3.distance( P1, P2 );\r\n\t\t\t\tvar bL = vec3.distance( P2, P3 );\r\n\t\t\t\tvar cL = vec3.distance( P3, P1 );\r\n\t\t\t\tvar s = (aL + bL+ cL) / 2;\r\n\t\t\t\ttotal_area += Math.sqrt(s * (s - aL) * (s - bL) * (s - cL));\r\n\t\t\t\tareas[i] = total_area;\r\n\t\t\t}\t\t\t\r\n\t\t\tfor(var i = 0; i < num_triangles; ++i) //normalize\r\n\t\t\t\tareas[i] /= total_area;\r\n\t\t}\r\n\r\n\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t{\r\n\t\t\tvar r = Math.random();\r\n\t\t\tvar index = evenly ? findRandomTriangle( areas, r ) : Math.floor(r * num_triangles );\r\n\t\t\t//get random triangle\r\n\t\t\tvar a = 0;\r\n\t\t\tvar b = 0;\r\n\t\t\tvar c = 0;\r\n\t\t\tif(indices)\r\n\t\t\t{\r\n\t\t\t\ta = indices[index*3]*3;\r\n\t\t\t\tb = indices[index*3+1]*3;\r\n\t\t\t\tc = indices[index*3+2]*3;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\ta = index*9;\r\n\t\t\t\tb = index*9+3;\r\n\t\t\t\tc = index*9+6;\r\n\t\t\t}\r\n\t\t\tvar s = Math.random();\r\n\t\t\tvar t = Math.random();\r\n\t\t\tvar sqrt_s = Math.sqrt(s);\r\n\t\t\tvar af = 1 - sqrt_s;\r\n\t\t\tvar bf = sqrt_s * ( 1 - t);\r\n\t\t\tvar cf = t * sqrt_s;\r\n\t\t\tpoints[i] = af * vertices[a] + bf*vertices[b] + cf*vertices[c];\r\n\t\t\tpoints[i+1] = af * vertices[a+1] + bf*vertices[b+1] + cf*vertices[c+1];\r\n\t\t\tpoints[i+2] = af * vertices[a+2] + bf*vertices[b+2] + cf*vertices[c+2];\r\n\t\t\tif(normals && mesh_normals)\r\n\t\t\t{\r\n\t\t\t\tnormals[i] = af * mesh_normals[a] + bf*mesh_normals[b] + cf*mesh_normals[c];\r\n\t\t\t\tnormals[i+1] = af * mesh_normals[a+1] + bf*mesh_normals[b+1] + cf*mesh_normals[c+1];\r\n\t\t\t\tnormals[i+2] = af * mesh_normals[a+2] + bf*mesh_normals[b+2] + cf*mesh_normals[c+2];\r\n\t\t\t\tvar N = normals.subarray(i,i+3);\r\n\t\t\t\tvec3.normalize(N,N);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphPoints3D.generateFromInsideObject = function( points, size, mesh )\r\n\t{\r\n\t\tif(!mesh || mesh.constructor !== GL.Mesh)\r\n\t\t\treturn;\r\n\r\n\t\tvar aabb = mesh.getBoundingBox();\r\n\t\tif(!mesh.octree)\r\n\t\t\tmesh.octree = new GL.Octree( mesh );\r\n\t\tvar octree = mesh.octree;\r\n\t\tvar origin = vec3.create();\r\n\t\tvar direction = vec3.fromValues(1,0,0);\r\n\t\tvar temp = vec3.create();\r\n\t\tvar i = 0;\r\n\t\tvar tries = 0;\r\n\t\twhile(i < size && tries < points.length * 10) //limit to avoid problems\r\n\t\t{\r\n\t\t\ttries += 1\r\n\t\t\tvar r = vec3.random(temp); //random point inside the aabb\r\n\t\t\tr[0] = (r[0] * 2 - 1) * aabb[3] + aabb[0];\r\n\t\t\tr[1] = (r[1] * 2 - 1) * aabb[4] + aabb[1];\r\n\t\t\tr[2] = (r[2] * 2 - 1) * aabb[5] + aabb[2];\r\n\t\t\torigin.set(r);\r\n\t\t\tvar hit = octree.testRay( origin, direction, 0, 10000, true, GL.Octree.ALL );\r\n\t\t\tif(!hit || hit.length % 2 == 0) //not inside\r\n\t\t\t\tcontinue;\r\n\t\t\tpoints.set( r, i );\r\n\t\t\ti+=3;\r\n\t\t}\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/points3D\", LGraphPoints3D );\r\n\r\n\r\n\r\n\tfunction LGraphPointsToInstances() {\r\n\t\tthis.addInput(\"points\", \"geometry\");\r\n\t\tthis.addOutput(\"instances\", \"[mat4]\");\r\n\t\tthis.properties = {\r\n\t\t\tmode: 1,\r\n\t\t\tautoupdate: true\r\n\t\t};\r\n\r\n\t\tthis.must_update = true;\r\n\t\tthis.matrices = [];\r\n\t\tthis.first_time = true;\r\n\t}\r\n\r\n\tLGraphPointsToInstances.NORMAL = 0;\r\n\tLGraphPointsToInstances.VERTICAL = 1;\r\n\tLGraphPointsToInstances.SPHERICAL = 2;\r\n\tLGraphPointsToInstances.RANDOM = 3;\r\n\tLGraphPointsToInstances.RANDOM_VERTICAL = 4;\r\n\r\n\tLGraphPointsToInstances.modes = {\"normal\":0,\"vertical\":1,\"spherical\":2,\"random\":3,\"random_vertical\":4};\r\n\tLGraphPointsToInstances.widgets_info = {\r\n\t\tmode: { widget: \"combo\", values: LGraphPointsToInstances.modes }\r\n\t};\r\n\r\n\tLGraphPointsToInstances.title = \"points to inst\";\r\n\r\n\tLGraphPointsToInstances.prototype.onExecute = function()\r\n\t{\r\n\t\tvar geo = this.getInputData(0);\r\n\t\tif( !geo )\r\n\t\t{\r\n\t\t\tthis.setOutputData(0,null);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif( !this.isOutputConnected(0) )\r\n\t\t\treturn;\r\n\r\n\t\tvar has_changed = (geo._version != this._version || geo._id != this._geometry_id);\r\n\r\n\t\tif( has_changed && this.properties.autoupdate || this.first_time )\r\n\t\t{\r\n\t\t\tthis.first_time = false;\r\n\t\t\tthis.updateInstances( geo );\r\n\t\t}\r\n\r\n\t\tthis.setOutputData( 0, this.matrices );\r\n\t}\r\n\r\n\tLGraphPointsToInstances.prototype.updateInstances = function( geometry )\r\n\t{\r\n\t\tvar vertices = geometry.vertices;\r\n\t\tif(!vertices)\r\n\t\t\treturn null;\r\n\t\tvar normals = geometry.normals;\r\n\r\n\t\tvar matrices = this.matrices;\r\n\t\tvar num_points = vertices.length / 3;\r\n\t\tif( matrices.length != num_points)\r\n\t\t\tmatrices.length = num_points;\r\n\t\tvar identity = mat4.create();\r\n\t\tvar temp = vec3.create();\r\n\t\tvar zero = vec3.create();\r\n\t\tvar UP = vec3.fromValues(0,1,0);\r\n\t\tvar FRONT = vec3.fromValues(0,0,-1);\r\n\t\tvar RIGHT = vec3.fromValues(1,0,0);\r\n\t\tvar R = quat.create();\r\n\r\n\t\tvar front = vec3.create();\r\n\t\tvar right = vec3.create();\r\n\t\tvar top = vec3.create();\r\n\r\n\t\tfor(var i = 0; i < vertices.length; i += 3)\r\n\t\t{\r\n\t\t\tvar index = i/3;\r\n\t\t\tvar m = matrices[index];\r\n\t\t\tif(!m)\r\n\t\t\t\tm = matrices[index] = mat4.create();\r\n\t\t\tm.set( identity );\r\n\t\t\tvar point = vertices.subarray(i,i+3);\r\n\r\n\t\t\tswitch(this.properties.mode)\r\n\t\t\t{\r\n\t\t\t\tcase LGraphPointsToInstances.NORMAL: \r\n\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\tif(normals)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar normal = normals.subarray(i,i+3);\r\n\t\t\t\t\t\ttop.set( normal );\r\n\t\t\t\t\t\tvec3.normalize( top, top );\r\n\t\t\t\t\t\tvec3.cross( right, FRONT, top );\r\n\t\t\t\t\t\tvec3.normalize( right, right );\r\n\t\t\t\t\t\tvec3.cross( front, right, top );\r\n\t\t\t\t\t\tvec3.normalize( front, front );\r\n\t\t\t\t\t\tm.set(right,0);\r\n\t\t\t\t\t\tm.set(top,4);\r\n\t\t\t\t\t\tm.set(front,8);\r\n\t\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase LGraphPointsToInstances.VERTICAL: \r\n\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase LGraphPointsToInstances.SPHERICAL: \r\n\t\t\t\t\tfront.set( point );\r\n\t\t\t\t\tvec3.normalize( front, front );\r\n\t\t\t\t\tvec3.cross( right, UP, front );\r\n\t\t\t\t\tvec3.normalize( right, right );\r\n\t\t\t\t\tvec3.cross( top, front, right );\r\n\t\t\t\t\tvec3.normalize( top, top );\r\n\t\t\t\t\tm.set(right,0);\r\n\t\t\t\t\tm.set(top,4);\r\n\t\t\t\t\tm.set(front,8);\r\n\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase LGraphPointsToInstances.RANDOM:\r\n\t\t\t\t\ttemp[0] = Math.random()*2 - 1;\r\n\t\t\t\t\ttemp[1] = Math.random()*2 - 1;\r\n\t\t\t\t\ttemp[2] = Math.random()*2 - 1;\r\n\t\t\t\t\tvec3.normalize( temp, temp );\r\n\t\t\t\t\tquat.setAxisAngle( R, temp, Math.random() * 2 * Math.PI );\r\n\t\t\t\t\tmat4.fromQuat(m, R);\r\n\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase LGraphPointsToInstances.RANDOM_VERTICAL:\r\n\t\t\t\t\tquat.setAxisAngle( R, UP, Math.random() * 2 * Math.PI );\r\n\t\t\t\t\tmat4.fromQuat(m, R);\r\n\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis._version = geometry._version;\r\n\t\tthis._geometry_id = geometry._id;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/points_to_instances\", LGraphPointsToInstances );\r\n\r\n\r\n\tfunction LGraphGeometryTransform() {\r\n\t\tthis.addInput(\"in\", \"geometry,[mat4]\");\r\n\t\tthis.addInput(\"mat4\", \"mat4\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\t\tthis.properties = {};\r\n\r\n\t\tthis.geometry = {\r\n\t\t\ttype: \"triangles\",\r\n\t\t\tvertices: null,\r\n\t\t\t_id: generateGeometryId(),\r\n\t\t\t_version: 0\r\n\t\t};\r\n\r\n\t\tthis._last_geometry_id = -1;\r\n\t\tthis._last_version = -1;\r\n\t\tthis._last_key = \"\";\r\n\r\n\t\tthis.must_update = true;\r\n\t}\r\n\r\n\tLGraphGeometryTransform.title = \"Transform\";\r\n\r\n\tLGraphGeometryTransform.prototype.onExecute = function() {\r\n\r\n\t\tvar input = this.getInputData(0);\r\n\t\tvar model = this.getInputData(1);\r\n\r\n\t\tif(!input)\r\n\t\t\treturn;\r\n\r\n\t\t//array of matrices\r\n\t\tif(input.constructor === Array)\r\n\t\t{\r\n\t\t\tif(input.length == 0)\r\n\t\t\t\treturn;\r\n\t\t\tthis.outputs[0].type = \"[mat4]\";\r\n\t\t\tif( !this.isOutputConnected(0) )\r\n\t\t\t\treturn;\r\n\r\n\t\t\tif(!model)\r\n\t\t\t{\r\n\t\t\t\tthis.setOutputData(0,input);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif(!this._output)\r\n\t\t\t\tthis._output = new Array();\r\n\t\t\tif(this._output.length != input.length)\r\n\t\t\t\tthis._output.length = input.length;\r\n\t\t\tfor(var i = 0; i < input.length; ++i)\r\n\t\t\t{\r\n\t\t\t\tvar m = this._output[i];\r\n\t\t\t\tif(!m)\r\n\t\t\t\t\tm = this._output[i] = mat4.create();\r\n\t\t\t\tmat4.multiply(m,input[i],model);\r\n\t\t\t}\r\n\t\t\tthis.setOutputData(0,this._output);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t//geometry\r\n\t\tif(!input.vertices || !input.vertices.length)\r\n\t\t\treturn;\r\n\t\tvar geo = input;\r\n\t\tthis.outputs[0].type = \"geometry\";\r\n\t\tif( !this.isOutputConnected(0) )\r\n\t\t\treturn;\r\n\t\tif(!model)\r\n\t\t{\r\n\t\t\tthis.setOutputData(0,geo);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar key = typedArrayToArray(model).join(\",\");\r\n\r\n\t\tif( this.must_update || geo._id != this._last_geometry_id || geo._version != this._last_version || key != this._last_key )\r\n\t\t{\r\n\t\t\tthis.updateGeometry(geo, model);\r\n\t\t\tthis._last_key = key;\r\n\t\t\tthis._last_version = geo._version;\r\n\t\t\tthis._last_geometry_id = geo._id;\r\n\t\t\tthis.must_update = false;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0,this.geometry);\r\n\t}\r\n\r\n\tLGraphGeometryTransform.prototype.updateGeometry = function(geometry, model) {\r\n\t\tvar old_vertices = geometry.vertices;\r\n\t\tvar vertices = this.geometry.vertices;\r\n\t\tif( !vertices || vertices.length != old_vertices.length )\r\n\t\t\tvertices = this.geometry.vertices = new Float32Array( old_vertices.length );\r\n\t\tvar temp = vec3.create();\r\n\r\n\t\tfor(var i = 0, l = vertices.length; i < l; i+=3)\r\n\t\t{\r\n\t\t\ttemp[0] = old_vertices[i]; temp[1] = old_vertices[i+1]; temp[2] = old_vertices[i+2]; \r\n\t\t\tmat4.multiplyVec3( temp, model, temp );\r\n\t\t\tvertices[i] = temp[0]; vertices[i+1] = temp[1]; vertices[i+2] = temp[2];\r\n\t\t}\r\n\r\n\t\tif(geometry.normals)\r\n\t\t{\r\n\t\t\tif( !this.geometry.normals || this.geometry.normals.length != geometry.normals.length )\r\n\t\t\t\tthis.geometry.normals = new Float32Array( geometry.normals.length );\r\n\t\t\tvar normals = this.geometry.normals;\r\n\t\t\tvar normal_model = mat4.invert(mat4.create(), model);\r\n\t\t\tif(normal_model)\r\n\t\t\t\tmat4.transpose(normal_model, normal_model);\r\n\t\t\tvar old_normals = geometry.normals;\r\n\t\t\tfor(var i = 0, l = normals.length; i < l; i+=3)\r\n\t\t\t{\r\n\t\t\t\ttemp[0] = old_normals[i]; temp[1] = old_normals[i+1]; temp[2] = old_normals[i+2]; \r\n\t\t\t\tmat4.multiplyVec3( temp, normal_model, temp );\r\n\t\t\t\tnormals[i] = temp[0]; normals[i+1] = temp[1]; normals[i+2] = temp[2];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.geometry.type = geometry.type;\r\n\t\tthis.geometry._version++;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/transform\", LGraphGeometryTransform );\r\n\r\n\r\n\tfunction LGraphGeometryPolygon() {\r\n\t\tthis.addInput(\"sides\", \"number\");\r\n\t\tthis.addInput(\"radius\", \"number\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\t\tthis.properties = { sides: 6, radius: 1, uvs: false }\r\n\r\n\t\tthis.geometry = {\r\n\t\t\ttype: \"line_loop\",\r\n\t\t\tvertices: null,\r\n\t\t\t_id: generateGeometryId()\r\n\t\t};\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.must_update = true;\r\n\r\n\t\tthis.last_info = { sides: -1, radius: -1 };\r\n\t}\r\n\r\n\tLGraphGeometryPolygon.title = \"Polygon\";\r\n\r\n\tLGraphGeometryPolygon.prototype.onExecute = function() {\r\n\r\n\t\tif( !this.isOutputConnected(0) )\r\n\t\t\treturn;\r\n\r\n\t\tvar sides = this.getInputOrProperty(\"sides\");\r\n\t\tvar radius = this.getInputOrProperty(\"radius\");\r\n\t\tsides = Math.max(3,sides)|0;\r\n\r\n\t\t//update\r\n\t\tif( this.last_info.sides != sides || this.last_info.radius != radius )\r\n\t\t\tthis.updateGeometry(sides, radius);\r\n\r\n\t\tthis.setOutputData(0,this.geometry);\r\n\t}\r\n\r\n\tLGraphGeometryPolygon.prototype.updateGeometry = function(sides, radius) {\r\n\t\tvar num = 3*sides;\r\n\t\tvar vertices = this.geometry.vertices;\r\n\t\tif( !vertices || vertices.length != num )\r\n\t\t\tvertices = this.geometry.vertices = new Float32Array( 3*sides );\r\n\t\tvar delta = (Math.PI * 2) / sides;\r\n\t\tvar gen_uvs = this.properties.uvs;\r\n\t\tif(gen_uvs)\r\n\t\t{\r\n\t\t\tuvs = this.geometry.coords = new Float32Array( 3*sides );\r\n\t\t}\r\n\r\n\r\n\t\tfor(var i = 0; i < sides; ++i)\r\n\t\t{\r\n\t\t\tvar angle = delta * -i;\r\n\t\t\tvar x = Math.cos( angle ) * radius;\r\n\t\t\tvar y = 0;\r\n\t\t\tvar z = Math.sin( angle ) * radius;\r\n\t\t\tvertices[i*3] = x;\r\n\t\t\tvertices[i*3+1] = y;\r\n\t\t\tvertices[i*3+2] = z;\r\n\r\n\t\t\tif(gen_uvs)\r\n\t\t\t{\r\n\t\t\t\t\r\n\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis.geometry._id = ++this.geometry_id;\r\n\t\tthis.geometry._version = ++this.version;\r\n\t\tthis.last_info.sides = sides;\r\n\t\tthis.last_info.radius = radius;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/polygon\", LGraphGeometryPolygon );\r\n\r\n\r\n\tfunction LGraphGeometryExtrude() {\r\n\r\n\t\tthis.addInput(\"\", \"geometry\");\r\n\t\tthis.addOutput(\"\", \"geometry\");\r\n\t\tthis.properties = { top_cap: true, bottom_cap: true, offset: [0,100,0] };\r\n\t\tthis.version = -1;\r\n\r\n\t\tthis._last_geo_version = -1;\r\n\t\tthis._must_update = true;\r\n\t}\r\n\r\n\tLGraphGeometryExtrude.title = \"extrude\";\r\n\r\n\tLGraphGeometryExtrude.prototype.onPropertyChanged = function(name, value)\r\n\t{\r\n\t\tthis._must_update = true;\r\n\t}\r\n\r\n\tLGraphGeometryExtrude.prototype.onExecute = function()\r\n\t{\r\n\t\tvar geo = this.getInputData(0);\r\n\t\tif( !geo || !this.isOutputConnected(0) )\r\n\t\t\treturn;\r\n\r\n\t\tif(geo.version != this._last_geo_version || this._must_update)\r\n\t\t{\r\n\t\t\tthis._geo = this.extrudeGeometry( geo, this._geo );\r\n\t\t\tif(this._geo)\r\n\t\t\t\tthis._geo.version = this.version++;\r\n\t\t\tthis._must_update = false;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0, this._geo);\r\n\t}\r\n\r\n\tLGraphGeometryExtrude.prototype.extrudeGeometry = function( geo )\r\n\t{\r\n\t\t//for every pair of vertices\r\n\t\tvar vertices = geo.vertices;\r\n\t\tvar num_points = vertices.length / 3;\r\n\r\n\t\tvar tempA = vec3.create();\r\n\t\tvar tempB = vec3.create();\r\n\t\tvar tempC = vec3.create();\r\n\t\tvar tempD = vec3.create();\r\n\t\tvar offset = new Float32Array( this.properties.offset );\r\n\r\n\t\tif(geo.type == \"line_loop\")\r\n\t\t{\r\n\t\t\tvar new_vertices = new Float32Array( num_points * 6 * 3 ); //every points become 6 ( caps not included )\r\n\t\t\tvar npos = 0;\r\n\t\t\tfor(var i = 0, l = vertices.length; i < l; i += 3)\r\n\t\t\t{\r\n\t\t\t\ttempA[0] = vertices[i]; tempA[1] = vertices[i+1]; tempA[2] = vertices[i+2];\r\n\r\n\t\t\t\tif( i+3 < l ) //loop\r\n\t\t\t\t{\r\n\t\t\t\t\ttempB[0] = vertices[i+3]; tempB[1] = vertices[i+4]; tempB[2] = vertices[i+5];\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\ttempB[0] = vertices[0]; tempB[1] = vertices[1]; tempB[2] = vertices[2];\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvec3.add( tempC, tempA, offset );\r\n\t\t\t\tvec3.add( tempD, tempB, offset );\r\n\r\n\t\t\t\tnew_vertices.set( tempA, npos ); npos += 3;\r\n\t\t\t\tnew_vertices.set( tempB, npos ); npos += 3;\r\n\t\t\t\tnew_vertices.set( tempC, npos ); npos += 3;\r\n\r\n\t\t\t\tnew_vertices.set( tempB, npos ); npos += 3;\r\n\t\t\t\tnew_vertices.set( tempD, npos ); npos += 3;\r\n\t\t\t\tnew_vertices.set( tempC, npos ); npos += 3;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar out_geo = {\r\n\t\t\t_id: generateGeometryId(),\r\n\t\t\ttype: \"triangles\",\r\n\t\t\tvertices: new_vertices\r\n\t\t};\r\n\r\n\t\treturn out_geo;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/extrude\", LGraphGeometryExtrude );\r\n\r\n\r\n\tfunction LGraphGeometryEval() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\r\n\t\tthis.properties = {\r\n\t\t\tcode: \"V[1] += 0.01 * Math.sin(I + T*0.001);\",\r\n\t\t\texecute_every_frame: false\r\n\t\t};\r\n\r\n\t\tthis.geometry = null;\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.must_update = true;\r\n\r\n\t\tthis.vertices = null;\r\n\t\tthis.func = null;\r\n\t}\r\n\r\n\tLGraphGeometryEval.title = \"geoeval\";\r\n\tLGraphGeometryEval.desc = \"eval code\";\r\n\r\n\tLGraphGeometryEval.widgets_info = {\r\n\t\tcode: { widget: \"code\" }\r\n\t};\r\n\r\n\tLGraphGeometryEval.prototype.onConfigure = function(o)\r\n\t{\r\n\t\tthis.compileCode();\r\n\t}\r\n\r\n\tLGraphGeometryEval.prototype.compileCode = function()\r\n\t{\r\n\t\tif(!this.properties.code)\r\n\t\t\treturn;\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tthis.func = new Function(\"V\",\"I\",\"T\", this.properties.code); \r\n\t\t\tthis.boxcolor = \"#AFA\";\r\n\t\t\tthis.must_update = true;\r\n\t\t}\r\n\t\tcatch (err)\r\n\t\t{\r\n\t\t\tthis.boxcolor = \"red\";\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphGeometryEval.prototype.onPropertyChanged = function(name, value)\r\n\t{\r\n\t\tif(name == \"code\")\r\n\t\t{\r\n\t\t\tthis.properties.code = value;\r\n\t\t\tthis.compileCode();\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphGeometryEval.prototype.onExecute = function() {\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\r\n\t\tif(!this.func)\r\n\t\t{\r\n\t\t\tthis.setOutputData(0,geometry);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif( this.geometry_id != geometry._id || this.version != geometry._version || this.must_update || this.properties.execute_every_frame )\r\n\t\t{\r\n\t\t\tthis.must_update = false;\r\n\t\t\tthis.geometry_id = geometry._id;\r\n\t\t\tif(this.properties.execute_every_frame)\r\n\t\t\t\tthis.version++;\r\n\t\t\telse\r\n\t\t\t\tthis.version = geometry._version;\r\n\t\t\tvar func = this.func;\r\n\t\t\tvar T = getTime();\r\n\r\n\t\t\t//clone\r\n\t\t\tif(!this.geometry)\r\n\t\t\t\tthis.geometry = {};\r\n\t\t\tfor(var i in geometry)\r\n\t\t\t{\r\n\t\t\t\tif(geometry[i] == null)\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\tif( geometry[i].constructor == Float32Array )\r\n\t\t\t\t\tthis.geometry[i] = new Float32Array( geometry[i] );\r\n\t\t\t\telse\r\n\t\t\t\t\tthis.geometry[i] = geometry[i];\r\n\t\t\t}\r\n\t\t\tthis.geometry._id = geometry._id;\r\n\t\t\tif(this.properties.execute_every_frame)\r\n\t\t\t\tthis.geometry._version = this.version;\r\n\t\t\telse\r\n\t\t\t\tthis.geometry._version = geometry._version + 1;\r\n\r\n\t\t\tvar V = vec3.create();\r\n\t\t\tvar vertices = this.vertices;\r\n\t\t\tif(!vertices || this.vertices.length != geometry.vertices.length)\r\n\t\t\t\tvertices = this.vertices = new Float32Array( geometry.vertices );\r\n\t\t\telse\r\n\t\t\t\tvertices.set( geometry.vertices );\r\n\t\t\tfor(var i = 0; i < vertices.length; i+=3)\r\n\t\t\t{\r\n\t\t\t\tV[0] = vertices[i];\r\n\t\t\t\tV[1] = vertices[i+1];\r\n\t\t\t\tV[2] = vertices[i+2];\r\n\t\t\t\tfunc(V,i/3,T);\r\n\t\t\t\tvertices[i] = V[0];\r\n\t\t\t\tvertices[i+1] = V[1];\r\n\t\t\t\tvertices[i+2] = V[2];\r\n\t\t\t}\r\n\t\t\tthis.geometry.vertices = vertices;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0,this.geometry);\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/eval\", LGraphGeometryEval );\r\n\r\n/*\r\nfunction LGraphGeometryDisplace() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addInput(\"img\", \"image\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\r\n\t\tthis.properties = {\r\n\t\t\tgrid_size: 1\r\n\t\t};\r\n\r\n\t\tthis.geometry = null;\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.must_update = true;\r\n\r\n\t\tthis.vertices = null;\r\n\t}\r\n\r\n\tLGraphGeometryDisplace.title = \"displace\";\r\n\tLGraphGeometryDisplace.desc = \"displace points\";\r\n\r\n\tLGraphGeometryDisplace.prototype.onExecute = function() {\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tvar image = this.getInputData(1);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\r\n\t\tif(!image)\r\n\t\t{\r\n\t\t\tthis.setOutputData(0,geometry);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif( this.geometry_id != geometry._id || this.version != geometry._version || this.must_update )\r\n\t\t{\r\n\t\t\tthis.must_update = false;\r\n\t\t\tthis.geometry_id = geometry._id;\r\n\t\t\tthis.version = geometry._version;\r\n\r\n\t\t\t//copy\r\n\t\t\tthis.geometry = {};\r\n\t\t\tfor(var i in geometry)\r\n\t\t\t\tthis.geometry[i] = geometry[i];\r\n\t\t\tthis.geometry._id = geometry._id;\r\n\t\t\tthis.geometry._version = geometry._version + 1;\r\n\r\n\t\t\tvar grid_size = this.properties.grid_size;\r\n\t\t\tif(grid_size != 0)\r\n\t\t\t{\r\n\t\t\t\tvar vertices = this.vertices;\r\n\t\t\t\tif(!vertices || this.vertices.length != this.geometry.vertices.length)\r\n\t\t\t\t\tvertices = this.vertices = new Float32Array( this.geometry.vertices );\r\n\t\t\t\tfor(var i = 0; i < vertices.length; i+=3)\r\n\t\t\t\t{\r\n\t\t\t\t\tvertices[i] = Math.round(vertices[i]/grid_size) * grid_size;\r\n\t\t\t\t\tvertices[i+1] = Math.round(vertices[i+1]/grid_size) * grid_size;\r\n\t\t\t\t\tvertices[i+2] = Math.round(vertices[i+2]/grid_size) * grid_size;\r\n\t\t\t\t}\r\n\t\t\t\tthis.geometry.vertices = vertices;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0,this.geometry);\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/displace\", LGraphGeometryDisplace );\r\n*/\r\n\r\n\tfunction LGraphConnectPoints() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\r\n\t\tthis.properties = {\r\n\t\t\tmin_dist: 0.4,\r\n\t\t\tmax_dist: 0.5,\r\n\t\t\tmax_connections: 0,\r\n\t\t\tprobability: 1\r\n\t\t};\r\n\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.my_version = 1;\r\n\t\tthis.must_update = true;\r\n\t}\r\n\r\n\tLGraphConnectPoints.title = \"connect points\";\r\n\tLGraphConnectPoints.desc = \"adds indices between near points\";\r\n\r\n\tLGraphConnectPoints.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tthis.must_update = true;\r\n\t}\r\n\r\n\tLGraphConnectPoints.prototype.onExecute = function() {\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\r\n\t\tif( this.geometry_id != geometry._id || this.version != geometry._version || this.must_update )\r\n\t\t{\r\n\t\t\tthis.must_update = false;\r\n\t\t\tthis.geometry_id = geometry._id;\r\n\t\t\tthis.version = geometry._version;\r\n\r\n\t\t\t//copy\r\n\t\t\tthis.geometry = {};\r\n\t\t\tfor(var i in geometry)\r\n\t\t\t\tthis.geometry[i] = geometry[i];\r\n\t\t\tthis.geometry._id = generateGeometryId();\r\n\t\t\tthis.geometry._version = this.my_version++;\r\n\r\n\t\t\tvar vertices = geometry.vertices;\r\n\t\t\tvar l = vertices.length;\r\n\t\t\tvar min_dist = this.properties.min_dist;\r\n\t\t\tvar max_dist = this.properties.max_dist;\r\n\t\t\tvar probability = this.properties.probability;\r\n\t\t\tvar max_connections = this.properties.max_connections;\r\n\t\t\tvar indices = [];\r\n\t\t\t\r\n\t\t\tfor(var i = 0; i < l; i+=3)\r\n\t\t\t{\r\n\t\t\t\tvar x = vertices[i];\r\n\t\t\t\tvar y = vertices[i+1];\r\n\t\t\t\tvar z = vertices[i+2];\r\n\t\t\t\tvar connections = 0;\r\n\t\t\t\tfor(var j = i+3; j < l; j+=3)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar x2 = vertices[j];\r\n\t\t\t\t\tvar y2 = vertices[j+1];\r\n\t\t\t\t\tvar z2 = vertices[j+2];\r\n\t\t\t\t\tvar dist = Math.sqrt( (x-x2)*(x-x2) + (y-y2)*(y-y2) + (z-z2)*(z-z2));\r\n\t\t\t\t\tif(dist > max_dist || dist < min_dist || (probability < 1 && probability < Math.random()) )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tindices.push(i/3,j/3);\r\n\t\t\t\t\tconnections += 1;\r\n\t\t\t\t\tif(max_connections && connections > max_connections)\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tthis.geometry.indices = this.indices = new Uint32Array(indices);\r\n\t\t}\r\n\r\n\t\tif(this.indices && this.indices.length)\r\n\t\t{\r\n\t\t\tthis.geometry.indices = this.indices;\r\n\t\t\tthis.setOutputData( 0, this.geometry );\r\n\t\t}\r\n\t\telse\r\n\t\t\tthis.setOutputData( 0, null );\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/connectPoints\", LGraphConnectPoints );\r\n\r\n    //Works with Litegl.js to create WebGL nodes\r\n    if (typeof GL == \"undefined\") //LiteGL RELATED **********************************************\r\n\t\treturn;\r\n\r\n\tfunction LGraphToGeometry() {\r\n\t\tthis.addInput(\"mesh\", \"mesh\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\r\n\t\tthis.geometry = {};\r\n\t\tthis.last_mesh = null;\r\n\t}\r\n\r\n\tLGraphToGeometry.title = \"to geometry\";\r\n\tLGraphToGeometry.desc = \"converts a mesh to geometry\";\r\n\r\n\tLGraphToGeometry.prototype.onExecute = function() {\r\n\t\tvar mesh = this.getInputData(0);\r\n\t\tif(!mesh)\r\n\t\t\treturn;\r\n\r\n\t\tif(mesh != this.last_mesh)\r\n\t\t{\r\n\t\t\tthis.last_mesh = mesh;\r\n\t\t\tfor(i in mesh.vertexBuffers)\r\n\t\t\t{\r\n\t\t\t\tvar buffer = mesh.vertexBuffers[i];\r\n\t\t\t\tthis.geometry[i] = buffer.data\r\n\t\t\t}\r\n\t\t\tif(mesh.indexBuffers[\"triangles\"])\r\n\t\t\t\tthis.geometry.indices = mesh.indexBuffers[\"triangles\"].data;\r\n\r\n\t\t\tthis.geometry._id = generateGeometryId();\r\n\t\t\tthis.geometry._version = 0;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0,this.geometry);\r\n\t\tif(this.geometry)\r\n\t\t\tthis.setOutputData(1,this.geometry.vertices);\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/toGeometry\", LGraphToGeometry );\r\n\r\n\tfunction LGraphGeometryToMesh() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addOutput(\"mesh\", \"mesh\");\r\n\t\tthis.properties = {};\r\n\t\tthis.version = -1;\r\n\t\tthis.mesh = null;\r\n\t}\r\n\r\n\tLGraphGeometryToMesh.title = \"Geo to Mesh\";\r\n\r\n\tLGraphGeometryToMesh.prototype.updateMesh = function(geometry)\r\n\t{\r\n\t\tif(!this.mesh)\r\n\t\t\tthis.mesh = new GL.Mesh();\r\n\r\n\t\tfor(var i in geometry)\r\n\t\t{\r\n\t\t\tif(i[0] == \"_\")\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tvar buffer_data = geometry[i];\r\n\r\n\t\t\tvar info = GL.Mesh.common_buffers[i];\r\n\t\t\tif(!info && i != \"indices\") //unknown buffer\r\n\t\t\t\tcontinue;\r\n\t\t\tvar spacing = info ? info.spacing : 3;\r\n\t\t\tvar mesh_buffer = this.mesh.vertexBuffers[i];\r\n\r\n\t\t\tif(!mesh_buffer || mesh_buffer.data.length != buffer_data.length)\r\n\t\t\t{\r\n\t\t\t\tmesh_buffer = new GL.Buffer( i == \"indices\" ? GL.ELEMENT_ARRAY_BUFFER : GL.ARRAY_BUFFER, buffer_data, spacing, GL.DYNAMIC_DRAW );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tmesh_buffer.data.set( buffer_data );\r\n\t\t\t\tmesh_buffer.upload(GL.DYNAMIC_DRAW);\r\n\t\t\t}\r\n\r\n\t\t\tthis.mesh.addBuffer( i, mesh_buffer );\r\n\t\t}\r\n\r\n\t\tif(this.mesh.vertexBuffers.normals &&this.mesh.vertexBuffers.normals.data.length != this.mesh.vertexBuffers.vertices.data.length )\r\n\t\t{\r\n\t\t\tvar n = new Float32Array([0,1,0]);\r\n\t\t\tvar normals = new Float32Array( this.mesh.vertexBuffers.vertices.data.length );\r\n\t\t\tfor(var i = 0; i < normals.length; i+= 3)\r\n\t\t\t\tnormals.set( n, i );\r\n\t\t\tmesh_buffer = new GL.Buffer( GL.ARRAY_BUFFER, normals, 3 );\r\n\t\t\tthis.mesh.addBuffer( \"normals\", mesh_buffer );\r\n\t\t}\r\n\r\n\t\tthis.mesh.updateBoundingBox();\r\n\t\tthis.geometry_id = this.mesh.id = geometry._id;\r\n\t\tthis.version = this.mesh.version = geometry._version;\r\n\t\treturn this.mesh;\r\n\t}\r\n\r\n\tLGraphGeometryToMesh.prototype.onExecute = function() {\r\n\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\t\tif( this.version != geometry._version || this.geometry_id != geometry._id )\r\n\t\t\tthis.updateMesh( geometry );\r\n\t\tthis.setOutputData(0, this.mesh);\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/toMesh\", LGraphGeometryToMesh );\r\n\r\n\tfunction LGraphRenderMesh() {\r\n\t\tthis.addInput(\"mesh\", \"mesh\");\r\n\t\tthis.addInput(\"mat4\", \"mat4\");\r\n\t\tthis.addInput(\"tex\", \"texture\");\r\n\r\n\t\tthis.properties = {\r\n\t\t\tenabled: true,\r\n\t\t\tprimitive: GL.TRIANGLES,\r\n\t\t\tadditive: false,\r\n\t\t\tcolor: [1,1,1],\r\n\t\t\topacity: 1\r\n\t\t};\r\n\r\n\t\tthis.color = vec4.create([1,1,1,1]);\r\n\t\tthis.model_matrix = mat4.create();\r\n\t\tthis.uniforms = {\r\n\t\t\tu_color: this.color,\r\n\t\t\tu_model: this.model_matrix\r\n\t\t};\r\n\t}\r\n\r\n\tLGraphRenderMesh.title = \"Render Mesh\";\r\n\tLGraphRenderMesh.desc = \"renders a mesh flat\";\r\n\r\n\tLGraphRenderMesh.PRIMITIVE_VALUES = { \"points\":GL.POINTS, \"lines\":GL.LINES, \"line_loop\":GL.LINE_LOOP,\"line_strip\":GL.LINE_STRIP, \"triangles\":GL.TRIANGLES, \"triangle_fan\":GL.TRIANGLE_FAN, \"triangle_strip\":GL.TRIANGLE_STRIP };\r\n\r\n\tLGraphRenderMesh.widgets_info = {\r\n\t\tprimitive: { widget: \"combo\", values: LGraphRenderMesh.PRIMITIVE_VALUES },\r\n\t\tcolor: { widget: \"color\" }\r\n\t};\r\n\r\n\tLGraphRenderMesh.prototype.onExecute = function() {\r\n\r\n\t\tif(!this.properties.enabled)\r\n\t\t\treturn;\r\n\r\n\t\tvar mesh = this.getInputData(0);\r\n\t\tif(!mesh)\r\n\t\t\treturn;\r\n\r\n\t\tif(!LiteGraph.LGraphRender.onRequestCameraMatrices)\r\n\t\t{\r\n\t\t\tconsole.warn(\"cannot render geometry, LiteGraph.onRequestCameraMatrices is null, remember to fill this with a callback(view_matrix, projection_matrix,viewprojection_matrix) to use 3D rendering from the graph\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tLiteGraph.LGraphRender.onRequestCameraMatrices( view_matrix, projection_matrix,viewprojection_matrix );\r\n\t\tvar shader = null;\r\n\t\tvar texture = this.getInputData(2);\r\n\t\tif(texture)\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"textured\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"textured\"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code, { USE_TEXTURE:\"\" });\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"flat\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"flat\"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code );\r\n\t\t}\r\n\r\n\t\tthis.color.set( this.properties.color );\r\n\t\tthis.color[3] = this.properties.opacity;\r\n\r\n\t\tvar model_matrix = this.model_matrix;\r\n\t\tvar m = this.getInputData(1);\r\n\t\tif(m)\r\n\t\t\tmodel_matrix.set(m);\r\n\t\telse\r\n\t\t\tmat4.identity( model_matrix );\r\n\r\n\t\tthis.uniforms.u_point_size = 1;\r\n\t\tvar primitive = this.properties.primitive;\r\n\r\n\t\tshader.uniforms( global_uniforms );\r\n\t\tshader.uniforms( this.uniforms );\r\n\r\n\t\tif(this.properties.opacity >= 1)\r\n\t\t\tgl.disable( gl.BLEND );\r\n\t\telse\r\n\t\t\tgl.enable( gl.BLEND );\r\n\t\tgl.enable( gl.DEPTH_TEST );\r\n\t\tif( this.properties.additive )\r\n\t\t{\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE );\r\n\t\t\tgl.depthMask( false );\r\n\t\t}\r\n\t\telse\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );\r\n\r\n\t\tvar indices = \"indices\";\r\n\t\tif( mesh.indexBuffers.triangles )\r\n\t\t\tindices = \"triangles\";\r\n\t\tshader.draw( mesh, primitive, indices );\r\n\t\tgl.disable( gl.BLEND );\r\n\t\tgl.depthMask( true );\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/render_mesh\", LGraphRenderMesh );\r\n\r\n\t//**************************\r\n\r\n\r\n\tfunction LGraphGeometryPrimitive() {\r\n\t\tthis.addInput(\"size\", \"number\");\r\n\t\tthis.addOutput(\"out\", \"mesh\");\r\n\t\tthis.properties = { type: 1, size: 1, subdivisions: 32 };\r\n\r\n\t\tthis.version = (Math.random() * 100000)|0;\r\n\t\tthis.last_info = { type: -1, size: -1, subdivisions: -1 };\r\n\t}\r\n\r\n\tLGraphGeometryPrimitive.title = \"Primitive\";\r\n\r\n\tLGraphGeometryPrimitive.VALID = { \"CUBE\":1, \"PLANE\":2, \"CYLINDER\":3, \"SPHERE\":4, \"CIRCLE\":5, \"HEMISPHERE\":6, \"ICOSAHEDRON\":7, \"CONE\":8, \"QUAD\":9 };\r\n\tLGraphGeometryPrimitive.widgets_info = {\r\n\t\ttype: { widget: \"combo\", values: LGraphGeometryPrimitive.VALID }\r\n\t};\r\n\r\n\tLGraphGeometryPrimitive.prototype.onExecute = function() {\r\n\r\n\t\tif( !this.isOutputConnected(0) )\r\n\t\t\treturn;\r\n\r\n\t\tvar size = this.getInputOrProperty(\"size\");\r\n\r\n\t\t//update\r\n\t\tif( this.last_info.type != this.properties.type || this.last_info.size != size || this.last_info.subdivisions != this.properties.subdivisions )\r\n\t\t\tthis.updateMesh( this.properties.type, size, this.properties.subdivisions );\r\n\r\n\t\tthis.setOutputData(0,this._mesh);\r\n\t}\r\n\r\n\tLGraphGeometryPrimitive.prototype.updateMesh = function(type, size, subdivisions)\r\n\t{\r\n\t\tsubdivisions = Math.max(0,subdivisions)|0;\r\n\r\n\t\tswitch (type)\r\n\t\t{\r\n\t\t\tcase 1: //CUBE: \r\n\t\t\t\tthis._mesh = GL.Mesh.cube({size: size, normals:true,coords:true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 2: //PLANE:\r\n\t\t\t\tthis._mesh = GL.Mesh.plane({size: size, xz: true, detail: subdivisions, normals:true,coords:true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 3: //CYLINDER:\r\n\t\t\t\tthis._mesh = GL.Mesh.cylinder({size: size, subdivisions: subdivisions, normals:true,coords:true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 4: //SPHERE:\r\n\t\t\t\tthis._mesh = GL.Mesh.sphere({size: size, \"long\": subdivisions, lat: subdivisions, normals:true,coords:true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 5: //CIRCLE:\r\n\t\t\t\tthis._mesh = GL.Mesh.circle({size: size, slices: subdivisions, normals:true, coords:true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 6: //HEMISPHERE:\r\n\t\t\t\tthis._mesh = GL.Mesh.sphere({size: size, \"long\": subdivisions, lat: subdivisions, normals:true, coords:true, hemi: true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 7: //ICOSAHEDRON:\r\n\t\t\t\tthis._mesh = GL.Mesh.icosahedron({size: size, subdivisions:subdivisions });\r\n\t\t\t\tbreak;\r\n\t\t\tcase 8: //CONE:\r\n\t\t\t\tthis._mesh = GL.Mesh.cone({radius: size, height: size, subdivisions:subdivisions });\r\n\t\t\t\tbreak;\r\n\t\t\tcase 9: //QUAD:\r\n\t\t\t\tthis._mesh = GL.Mesh.plane({size: size, xz: false, detail: subdivisions, normals:true, coords:true });\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\r\n\t\tthis.last_info.type = type;\r\n\t\tthis.last_info.size = size;\r\n\t\tthis.last_info.subdivisions = subdivisions;\r\n\t\tthis._mesh.version = this.version++;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/mesh_primitive\", LGraphGeometryPrimitive );\r\n\r\n\r\n\tfunction LGraphRenderPoints() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addInput(\"mat4\", \"mat4\");\r\n\t\tthis.addInput(\"tex\", \"texture\");\r\n\t\tthis.properties = {\r\n\t\t\tenabled: true,\r\n\t\t\tpoint_size: 0.1,\r\n\t\t\tfixed_size: false,\r\n\t\t\tadditive: true,\r\n\t\t\tcolor: [1,1,1],\r\n\t\t\topacity: 1\r\n\t\t};\r\n\r\n\t\tthis.color = vec4.create([1,1,1,1]);\r\n\r\n\t\tthis.uniforms = {\r\n\t\t\tu_point_size: 1,\r\n\t\t\tu_perspective: 1,\r\n\t\t\tu_point_perspective: 1,\r\n\t\t\tu_color: this.color\r\n\t\t};\r\n\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.mesh = null;\r\n\t}\r\n\r\n\tLGraphRenderPoints.title = \"renderPoints\";\r\n\tLGraphRenderPoints.desc = \"render points with a texture\";\r\n\r\n\tLGraphRenderPoints.widgets_info = {\r\n\t\tcolor: { widget: \"color\" }\r\n\t};\r\n\r\n\tLGraphRenderPoints.prototype.updateMesh = function(geometry)\r\n\t{\r\n\t\tvar buffer = this.buffer;\r\n\t\tif(!this.buffer || !this.buffer.data || this.buffer.data.length != geometry.vertices.length)\r\n\t\t\tthis.buffer = new GL.Buffer( GL.ARRAY_BUFFER, geometry.vertices,3,GL.DYNAMIC_DRAW);\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.buffer.data.set( geometry.vertices );\r\n\t\t\tthis.buffer.upload(GL.DYNAMIC_DRAW);\r\n\t\t}\r\n\r\n\t\tif(!this.mesh)\r\n\t\t\tthis.mesh = new GL.Mesh();\r\n\r\n\t\tthis.mesh.addBuffer(\"vertices\",this.buffer);\r\n\t\tthis.geometry_id = this.mesh.id = geometry._id;\r\n\t\tthis.version = this.mesh.version = geometry._version;\r\n\t}\r\n\r\n\tLGraphRenderPoints.prototype.onExecute = function() {\r\n\r\n\t\tif(!this.properties.enabled)\r\n\t\t\treturn;\r\n\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\t\tif(this.version != geometry._version || this.geometry_id != geometry._id )\r\n\t\t\tthis.updateMesh( geometry );\r\n\r\n\t\tif(!LiteGraph.LGraphRender.onRequestCameraMatrices)\r\n\t\t{\r\n\t\t\tconsole.warn(\"cannot render geometry, LiteGraph.onRequestCameraMatrices is null, remember to fill this with a callback(view_matrix, projection_matrix,viewprojection_matrix) to use 3D rendering from the graph\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tLiteGraph.LGraphRender.onRequestCameraMatrices( view_matrix, projection_matrix,viewprojection_matrix );\r\n\t\tvar shader = null;\r\n\r\n\t\tvar texture = this.getInputData(2);\r\n\t\t\r\n\t\tif(texture)\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"textured_points\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"textured_points\"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code, { USE_TEXTURED_POINTS:\"\" });\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"points\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"points\"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code, { USE_POINTS: \"\" });\r\n\t\t}\r\n\r\n\t\tthis.color.set( this.properties.color );\r\n\t\tthis.color[3] = this.properties.opacity;\r\n\r\n\t\tvar m = this.getInputData(1);\r\n\t\tif(m)\r\n\t\t\tmodel_matrix.set(m);\r\n\t\telse\r\n\t\t\tmat4.identity( model_matrix );\r\n\r\n\t\tthis.uniforms.u_point_size = this.properties.point_size;\r\n\t\tthis.uniforms.u_point_perspective = this.properties.fixed_size ? 0 : 1;\r\n\t\tthis.uniforms.u_perspective = gl.viewport_data[3] * projection_matrix[5];\r\n\r\n\t\tshader.uniforms( global_uniforms );\r\n\t\tshader.uniforms( this.uniforms );\r\n\r\n\t\tif(this.properties.opacity >= 1)\r\n\t\t\tgl.disable( gl.BLEND );\r\n\t\telse\r\n\t\t\tgl.enable( gl.BLEND );\r\n\r\n\t\tgl.enable( gl.DEPTH_TEST );\r\n\t\tif( this.properties.additive )\r\n\t\t{\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE );\r\n\t\t\tgl.depthMask( false );\r\n\t\t}\r\n\t\telse\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );\r\n\r\n\t\tshader.draw( this.mesh, GL.POINTS );\r\n\r\n\t\tgl.disable( gl.BLEND );\r\n\t\tgl.depthMask( true );\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/render_points\", LGraphRenderPoints );\r\n\r\n\tLGraphRenderPoints.vertex_shader_code = '\\\r\n\t\tprecision mediump float;\\n\\\r\n\t\tattribute vec3 a_vertex;\\n\\\r\n\t\tvarying vec3 v_vertex;\\n\\\r\n\t\tattribute vec3 a_normal;\\n\\\r\n\t\tvarying vec3 v_normal;\\n\\\r\n\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\tattribute vec4 a_color;\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tattribute vec2 a_coord;\\n\\\r\n\t\tvarying vec2 v_coord;\\n\\\r\n\t\t#ifdef USE_SIZE\\n\\\r\n\t\t\tattribute float a_extra;\\n\\\r\n\t\t#endif\\n\\\r\n\t\t#ifdef USE_INSTANCING\\n\\\r\n\t\t\tattribute mat4 u_model;\\n\\\r\n\t\t#else\\n\\\r\n\t\t\tuniform mat4 u_model;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tuniform mat4 u_viewprojection;\\n\\\r\n\t\tuniform float u_point_size;\\n\\\r\n\t\tuniform float u_perspective;\\n\\\r\n\t\tuniform float u_point_perspective;\\n\\\r\n\t\tfloat computePointSize(float radius, float w)\\n\\\r\n\t\t{\\n\\\r\n\t\t\tif(radius < 0.0)\\n\\\r\n\t\t\t\treturn -radius;\\n\\\r\n\t\t\treturn u_perspective * radius / w;\\n\\\r\n\t\t}\\n\\\r\n\t\tvoid main() {\\n\\\r\n\t\t\tv_coord = a_coord;\\n\\\r\n\t\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\t\tv_color = a_color;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tv_vertex = ( u_model * vec4( a_vertex, 1.0 )).xyz;\\n\\\r\n\t\t\tv_normal = ( u_model * vec4( a_normal, 0.0 )).xyz;\\n\\\r\n\t\t\tgl_Position = u_viewprojection * vec4(v_vertex,1.0);\\n\\\r\n\t\t\tgl_PointSize = u_point_size;\\n\\\r\n\t\t\t#ifdef USE_SIZE\\n\\\r\n\t\t\t\tgl_PointSize = a_extra;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tif(u_point_perspective != 0.0)\\n\\\r\n\t\t\t\tgl_PointSize = computePointSize( gl_PointSize, gl_Position.w );\\n\\\r\n\t\t}\\\r\n\t';\r\n\r\n\tLGraphRenderPoints.fragment_shader_code = '\\\r\n\t\tprecision mediump float;\\n\\\r\n\t\tuniform vec4 u_color;\\n\\\r\n\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tvarying vec2 v_coord;\\n\\\r\n\t\tuniform sampler2D u_texture;\\n\\\r\n\t\tvoid main() {\\n\\\r\n\t\t\tvec4 color = u_color;\\n\\\r\n\t\t\t#ifdef USE_TEXTURED_POINTS\\n\\\r\n\t\t\t\tcolor *= texture2D(u_texture, gl_PointCoord.xy);\\n\\\r\n\t\t\t#else\\n\\\r\n\t\t\t\t#ifdef USE_TEXTURE\\n\\\r\n\t\t\t\t  color *= texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\t  if(color.a < 0.1)\\n\\\r\n\t\t\t\t\tdiscard;\\n\\\r\n\t\t\t\t#endif\\n\\\r\n\t\t\t\t#ifdef USE_POINTS\\n\\\r\n\t\t\t\t\tfloat dist = length( gl_PointCoord.xy - vec2(0.5) );\\n\\\r\n\t\t\t\t\tif( dist > 0.45 )\\n\\\r\n\t\t\t\t\t\tdiscard;\\n\\\r\n\t\t\t\t#endif\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\t\tcolor *= v_color;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tgl_FragColor = color;\\n\\\r\n\t\t}\\\r\n\t';\r\n\r\n\t//based on https://inconvergent.net/2019/depth-of-field/\r\n\t/*\r\n\tfunction LGraphRenderGeometryDOF() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addInput(\"mat4\", \"mat4\");\r\n\t\tthis.addInput(\"tex\", \"texture\");\r\n\t\tthis.properties = {\r\n\t\t\tenabled: true,\r\n\t\t\tlines: true,\r\n\t\t\tpoint_size: 0.1,\r\n\t\t\tfixed_size: false,\r\n\t\t\tadditive: true,\r\n\t\t\tcolor: [1,1,1],\r\n\t\t\topacity: 1\r\n\t\t};\r\n\r\n\t\tthis.color = vec4.create([1,1,1,1]);\r\n\r\n\t\tthis.uniforms = {\r\n\t\t\tu_point_size: 1,\r\n\t\t\tu_perspective: 1,\r\n\t\t\tu_point_perspective: 1,\r\n\t\t\tu_color: this.color\r\n\t\t};\r\n\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.mesh = null;\r\n\t}\r\n\r\n\tLGraphRenderGeometryDOF.widgets_info = {\r\n\t\tcolor: { widget: \"color\" }\r\n\t};\r\n\r\n\tLGraphRenderGeometryDOF.prototype.updateMesh = function(geometry)\r\n\t{\r\n\t\tvar buffer = this.buffer;\r\n\t\tif(!this.buffer || this.buffer.data.length != geometry.vertices.length)\r\n\t\t\tthis.buffer = new GL.Buffer( GL.ARRAY_BUFFER, geometry.vertices,3,GL.DYNAMIC_DRAW);\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.buffer.data.set( geometry.vertices );\r\n\t\t\tthis.buffer.upload(GL.DYNAMIC_DRAW);\r\n\t\t}\r\n\r\n\t\tif(!this.mesh)\r\n\t\t\tthis.mesh = new GL.Mesh();\r\n\r\n\t\tthis.mesh.addBuffer(\"vertices\",this.buffer);\r\n\t\tthis.geometry_id = this.mesh.id = geometry._id;\r\n\t\tthis.version = this.mesh.version = geometry._version;\r\n\t}\r\n\r\n\tLGraphRenderGeometryDOF.prototype.onExecute = function() {\r\n\r\n\t\tif(!this.properties.enabled)\r\n\t\t\treturn;\r\n\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\t\tif(this.version != geometry._version || this.geometry_id != geometry._id )\r\n\t\t\tthis.updateMesh( geometry );\r\n\r\n\t\tif(!LiteGraph.LGraphRender.onRequestCameraMatrices)\r\n\t\t{\r\n\t\t\tconsole.warn(\"cannot render geometry, LiteGraph.onRequestCameraMatrices is null, remember to fill this with a callback(view_matrix, projection_matrix,viewprojection_matrix) to use 3D rendering from the graph\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tLiteGraph.LGraphRender.onRequestCameraMatrices( view_matrix, projection_matrix,viewprojection_matrix );\r\n\t\tvar shader = null;\r\n\r\n\t\tvar texture = this.getInputData(2);\r\n\t\t\r\n\t\tif(texture)\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"textured_points\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"textured_points\"] = new GL.Shader( LGraphRenderGeometryDOF.vertex_shader_code, LGraphRenderGeometryDOF.fragment_shader_code, { USE_TEXTURED_POINTS:\"\" });\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"points\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"points\"] = new GL.Shader( LGraphRenderGeometryDOF.vertex_shader_code, LGraphRenderGeometryDOF.fragment_shader_code, { USE_POINTS: \"\" });\r\n\t\t}\r\n\r\n\t\tthis.color.set( this.properties.color );\r\n\t\tthis.color[3] = this.properties.opacity;\r\n\r\n\t\tvar m = this.getInputData(1);\r\n\t\tif(m)\r\n\t\t\tmodel_matrix.set(m);\r\n\t\telse\r\n\t\t\tmat4.identity( model_matrix );\r\n\r\n\t\tthis.uniforms.u_point_size = this.properties.point_size;\r\n\t\tthis.uniforms.u_point_perspective = this.properties.fixed_size ? 0 : 1;\r\n\t\tthis.uniforms.u_perspective = gl.viewport_data[3] * projection_matrix[5];\r\n\r\n\t\tshader.uniforms( global_uniforms );\r\n\t\tshader.uniforms( this.uniforms );\r\n\r\n\t\tif(this.properties.opacity >= 1)\r\n\t\t\tgl.disable( gl.BLEND );\r\n\t\telse\r\n\t\t\tgl.enable( gl.BLEND );\r\n\r\n\t\tgl.enable( gl.DEPTH_TEST );\r\n\t\tif( this.properties.additive )\r\n\t\t{\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE );\r\n\t\t\tgl.depthMask( false );\r\n\t\t}\r\n\t\telse\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );\r\n\r\n\t\tshader.draw( this.mesh, GL.POINTS );\r\n\r\n\t\tgl.disable( gl.BLEND );\r\n\t\tgl.depthMask( true );\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/render_dof\", LGraphRenderGeometryDOF );\r\n\r\n\tLGraphRenderGeometryDOF.vertex_shader_code = '\\\r\n\t\tprecision mediump float;\\n\\\r\n\t\tattribute vec3 a_vertex;\\n\\\r\n\t\tvarying vec3 v_vertex;\\n\\\r\n\t\tattribute vec3 a_normal;\\n\\\r\n\t\tvarying vec3 v_normal;\\n\\\r\n\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\tattribute vec4 a_color;\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tattribute vec2 a_coord;\\n\\\r\n\t\tvarying vec2 v_coord;\\n\\\r\n\t\t#ifdef USE_SIZE\\n\\\r\n\t\t\tattribute float a_extra;\\n\\\r\n\t\t#endif\\n\\\r\n\t\t#ifdef USE_INSTANCING\\n\\\r\n\t\t\tattribute mat4 u_model;\\n\\\r\n\t\t#else\\n\\\r\n\t\t\tuniform mat4 u_model;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tuniform mat4 u_viewprojection;\\n\\\r\n\t\tuniform float u_point_size;\\n\\\r\n\t\tuniform float u_perspective;\\n\\\r\n\t\tuniform float u_point_perspective;\\n\\\r\n\t\tfloat computePointSize(float radius, float w)\\n\\\r\n\t\t{\\n\\\r\n\t\t\tif(radius < 0.0)\\n\\\r\n\t\t\t\treturn -radius;\\n\\\r\n\t\t\treturn u_perspective * radius / w;\\n\\\r\n\t\t}\\n\\\r\n\t\tvoid main() {\\n\\\r\n\t\t\tv_coord = a_coord;\\n\\\r\n\t\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\t\tv_color = a_color;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tv_vertex = ( u_model * vec4( a_vertex, 1.0 )).xyz;\\n\\\r\n\t\t\tv_normal = ( u_model * vec4( a_normal, 0.0 )).xyz;\\n\\\r\n\t\t\tgl_Position = u_viewprojection * vec4(v_vertex,1.0);\\n\\\r\n\t\t\tgl_PointSize = u_point_size;\\n\\\r\n\t\t\t#ifdef USE_SIZE\\n\\\r\n\t\t\t\tgl_PointSize = a_extra;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tif(u_point_perspective != 0.0)\\n\\\r\n\t\t\t\tgl_PointSize = computePointSize( gl_PointSize, gl_Position.w );\\n\\\r\n\t\t}\\\r\n\t';\r\n\r\n\tLGraphRenderGeometryDOF.fragment_shader_code = '\\\r\n\t\tprecision mediump float;\\n\\\r\n\t\tuniform vec4 u_color;\\n\\\r\n\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tvarying vec2 v_coord;\\n\\\r\n\t\tuniform sampler2D u_texture;\\n\\\r\n\t\tvoid main() {\\n\\\r\n\t\t\tvec4 color = u_color;\\n\\\r\n\t\t\t#ifdef USE_TEXTURED_POINTS\\n\\\r\n\t\t\t\tcolor *= texture2D(u_texture, gl_PointCoord.xy);\\n\\\r\n\t\t\t#else\\n\\\r\n\t\t\t\t#ifdef USE_TEXTURE\\n\\\r\n\t\t\t\t  color *= texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\t  if(color.a < 0.1)\\n\\\r\n\t\t\t\t\tdiscard;\\n\\\r\n\t\t\t\t#endif\\n\\\r\n\t\t\t\t#ifdef USE_POINTS\\n\\\r\n\t\t\t\t\tfloat dist = length( gl_PointCoord.xy - vec2(0.5) );\\n\\\r\n\t\t\t\t\tif( dist > 0.45 )\\n\\\r\n\t\t\t\t\t\tdiscard;\\n\\\r\n\t\t\t\t#endif\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\t\tcolor *= v_color;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tgl_FragColor = color;\\n\\\r\n\t\t}\\\r\n\t';\r\n\t*/\r\n\r\n\r\n\r\n})(this);\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n    var LGraphTexture = global.LGraphTexture;\r\n\r\n    //Works with Litegl.js to create WebGL nodes\r\n    if (typeof GL != \"undefined\") {\r\n        // Texture Lens *****************************************\r\n        function LGraphFXLens() {\r\n            this.addInput(\"Texture\", \"Texture\");\r\n            this.addInput(\"Aberration\", \"number\");\r\n            this.addInput(\"Distortion\", \"number\");\r\n            this.addInput(\"Blur\", \"number\");\r\n            this.addOutput(\"Texture\", \"Texture\");\r\n            this.properties = {\r\n                aberration: 1.0,\r\n                distortion: 1.0,\r\n                blur: 1.0,\r\n                precision: LGraphTexture.DEFAULT\r\n            };\r\n\r\n            if (!LGraphFXLens._shader) {\r\n                LGraphFXLens._shader = new GL.Shader(\r\n                    GL.Shader.SCREEN_VERTEX_SHADER,\r\n                    LGraphFXLens.pixel_shader\r\n                );\r\n                LGraphFXLens._texture = new GL.Texture(3, 1, {\r\n                    format: gl.RGB,\r\n                    wrap: gl.CLAMP_TO_EDGE,\r\n                    magFilter: gl.LINEAR,\r\n                    minFilter: gl.LINEAR,\r\n                    pixel_data: [255, 0, 0, 0, 255, 0, 0, 0, 255]\r\n                });\r\n            }\r\n        }\r\n\r\n        LGraphFXLens.title = \"Lens\";\r\n        LGraphFXLens.desc = \"Camera Lens distortion\";\r\n        LGraphFXLens.widgets_info = {\r\n            precision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\r\n        };\r\n\r\n        LGraphFXLens.prototype.onExecute = function() {\r\n            var tex = this.getInputData(0);\r\n            if (this.properties.precision === LGraphTexture.PASS_THROUGH) {\r\n                this.setOutputData(0, tex);\r\n                return;\r\n            }\r\n\r\n            if (!tex) {\r\n                return;\r\n            }\r\n\r\n            this._tex = LGraphTexture.getTargetTexture(\r\n                tex,\r\n                this._tex,\r\n                this.properties.precision\r\n            );\r\n\r\n            var aberration = this.properties.aberration;\r\n            if (this.isInputConnected(1)) {\r\n                aberration = this.getInputData(1);\r\n                this.properties.aberration = aberration;\r\n            }\r\n\r\n            var distortion = this.properties.distortion;\r\n            if (this.isInputConnected(2)) {\r\n                distortion = this.getInputData(2);\r\n                this.properties.distortion = distortion;\r\n            }\r\n\r\n            var blur = this.properties.blur;\r\n            if (this.isInputConnected(3)) {\r\n                blur = this.getInputData(3);\r\n                this.properties.blur = blur;\r\n            }\r\n\r\n            gl.disable(gl.BLEND);\r\n            gl.disable(gl.DEPTH_TEST);\r\n            var mesh = Mesh.getScreenQuad();\r\n            var shader = LGraphFXLens._shader;\r\n            //var camera = LS.Renderer._current_camera;\r\n\r\n            this._tex.drawTo(function() {\r\n                tex.bind(0);\r\n                shader\r\n                    .uniforms({\r\n                        u_texture: 0,\r\n                        u_aberration: aberration,\r\n                        u_distortion: distortion,\r\n                        u_blur: blur\r\n                    })\r\n                    .draw(mesh);\r\n            });\r\n\r\n            this.setOutputData(0, this._tex);\r\n        };\r\n\r\n        LGraphFXLens.pixel_shader =\r\n            \"precision highp float;\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec2 u_camera_planes;\\n\\\r\n\t\t\tuniform float u_aberration;\\n\\\r\n\t\t\tuniform float u_distortion;\\n\\\r\n\t\t\tuniform float u_blur;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec2 coord = v_coord;\\n\\\r\n\t\t\t\tfloat dist = distance(vec2(0.5), coord);\\n\\\r\n\t\t\t\tvec2 dist_coord = coord - vec2(0.5);\\n\\\r\n\t\t\t\tfloat percent = 1.0 + ((0.5 - dist) / 0.5) * u_distortion;\\n\\\r\n\t\t\t\tdist_coord *= percent;\\n\\\r\n\t\t\t\tcoord = dist_coord + vec2(0.5);\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture,coord, u_blur * dist);\\n\\\r\n\t\t\t\tcolor.r = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0+0.01*u_aberration), u_blur * dist ).r;\\n\\\r\n\t\t\t\tcolor.b = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0-0.01*u_aberration), u_blur * dist ).b;\\n\\\r\n\t\t\t\tgl_FragColor = color;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n        /*\r\n\t\t\tfloat normalized_tunable_sigmoid(float xs, float k)\\n\\\r\n\t\t\t{\\n\\\r\n\t\t\t\txs = xs * 2.0 - 1.0;\\n\\\r\n\t\t\t\tfloat signx = sign(xs);\\n\\\r\n\t\t\t\tfloat absx = abs(xs);\\n\\\r\n\t\t\t\treturn signx * ((-k - 1.0)*absx)/(2.0*(-2.0*k*absx+k-1.0)) + 0.5;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t*/\r\n\r\n        LiteGraph.registerNodeType(\"fx/lens\", LGraphFXLens);\r\n        global.LGraphFXLens = LGraphFXLens;\r\n\r\n        /* not working yet\r\n\tfunction LGraphDepthOfField()\r\n\t{\r\n\t\tthis.addInput(\"Color\",\"Texture\");\r\n\t\tthis.addInput(\"Linear Depth\",\"Texture\");\r\n\t\tthis.addInput(\"Camera\",\"camera\");\r\n\t\tthis.addOutput(\"Texture\",\"Texture\");\r\n\t\tthis.properties = { high_precision: false };\r\n\t}\r\n\r\n\tLGraphDepthOfField.title = \"Depth Of Field\";\r\n\tLGraphDepthOfField.desc = \"Applies a depth of field effect\";\r\n\r\n\tLGraphDepthOfField.prototype.onExecute = function()\r\n\t{\r\n\t\tvar tex = this.getInputData(0);\r\n\t\tvar depth = this.getInputData(1);\r\n\t\tvar camera = this.getInputData(2);\r\n\r\n\t\tif(!tex || !depth || !camera) \r\n\t\t{\r\n\t\t\tthis.setOutputData(0, tex);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar precision = gl.UNSIGNED_BYTE;\r\n\t\tif(this.properties.high_precision)\r\n\t\t\tprecision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT;\t\t\t\r\n\t\tif(!this._temp_texture || this._temp_texture.type != precision ||\r\n\t\t\tthis._temp_texture.width != tex.width || this._temp_texture.height != tex.height)\r\n\t\t\tthis._temp_texture = new GL.Texture( tex.width, tex.height, { type: precision, format: gl.RGBA, filter: gl.LINEAR });\r\n\r\n\t\tvar shader = LGraphDepthOfField._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphDepthOfField._pixel_shader );\r\n\r\n\t\tvar screen_mesh = Mesh.getScreenQuad();\r\n\r\n\t\tgl.disable( gl.DEPTH_TEST );\r\n\t\tgl.disable( gl.BLEND );\r\n\r\n\t\tvar camera_position = camera.getEye();\r\n\t\tvar focus_point = camera.getCenter();\r\n\t\tvar distance = vec3.distance( camera_position, focus_point );\r\n\t\tvar far = camera.far;\r\n\t\tvar focus_range = distance * 0.5;\r\n\r\n\t\tthis._temp_texture.drawTo( function() {\r\n\t\t\ttex.bind(0);\r\n\t\t\tdepth.bind(1);\r\n\t\t\tshader.uniforms({u_texture:0, u_depth_texture:1, u_resolution: [1/tex.width, 1/tex.height], u_far: far, u_focus_point: distance, u_focus_scale: focus_range }).draw(screen_mesh);\r\n\t\t});\r\n\r\n\t\tthis.setOutputData(0, this._temp_texture);\r\n\t}\r\n\r\n\t//from http://tuxedolabs.blogspot.com.es/2018/05/bokeh-depth-of-field-in-single-pass.html\r\n\tLGraphDepthOfField._pixel_shader = \"\\n\\\r\n\t\tprecision highp float;\\n\\\r\n\t\tvarying vec2 v_coord;\\n\\\r\n\t\tuniform sampler2D u_texture; //Image to be processed\\n\\\r\n\t\tuniform sampler2D u_depth_texture; //Linear depth, where 1.0 == far plane\\n\\\r\n\t\tuniform vec2 u_iresolution; //The size of a pixel: vec2(1.0/width, 1.0/height)\\n\\\r\n\t\tuniform float u_far; // Far plane\\n\\\r\n\t\tuniform float u_focus_point;\\n\\\r\n\t\tuniform float u_focus_scale;\\n\\\r\n\t\t\\n\\\r\n\t\tconst float GOLDEN_ANGLE = 2.39996323;\\n\\\r\n\t\tconst float MAX_BLUR_SIZE = 20.0;\\n\\\r\n\t\tconst float RAD_SCALE = 0.5; // Smaller = nicer blur, larger = faster\\n\\\r\n\t\t\\n\\\r\n\t\tfloat getBlurSize(float depth, float focusPoint, float focusScale)\\n\\\r\n\t\t{\\n\\\r\n\t\t float coc = clamp((1.0 / focusPoint - 1.0 / depth)*focusScale, -1.0, 1.0);\\n\\\r\n\t\t return abs(coc) * MAX_BLUR_SIZE;\\n\\\r\n\t\t}\\n\\\r\n\t\t\\n\\\r\n\t\tvec3 depthOfField(vec2 texCoord, float focusPoint, float focusScale)\\n\\\r\n\t\t{\\n\\\r\n\t\t float centerDepth = texture2D(u_depth_texture, texCoord).r * u_far;\\n\\\r\n\t\t float centerSize = getBlurSize(centerDepth, focusPoint, focusScale);\\n\\\r\n\t\t vec3 color = texture2D(u_texture, v_coord).rgb;\\n\\\r\n\t\t float tot = 1.0;\\n\\\r\n\t\t\\n\\\r\n\t\t float radius = RAD_SCALE;\\n\\\r\n\t\t for (float ang = 0.0; ang < 100.0; ang += GOLDEN_ANGLE)\\n\\\r\n\t\t {\\n\\\r\n\t\t  vec2 tc = texCoord + vec2(cos(ang), sin(ang)) * u_iresolution * radius;\\n\\\r\n\t\t\t\\n\\\r\n\t\t  vec3 sampleColor = texture2D(u_texture, tc).rgb;\\n\\\r\n\t\t  float sampleDepth = texture2D(u_depth_texture, tc).r * u_far;\\n\\\r\n\t\t  float sampleSize = getBlurSize( sampleDepth, focusPoint, focusScale );\\n\\\r\n\t\t  if (sampleDepth > centerDepth)\\n\\\r\n\t\t   sampleSize = clamp(sampleSize, 0.0, centerSize*2.0);\\n\\\r\n\t\t\t\\n\\\r\n\t\t  float m = smoothstep(radius-0.5, radius+0.5, sampleSize);\\n\\\r\n\t\t  color += mix(color/tot, sampleColor, m);\\n\\\r\n\t\t  tot += 1.0;\\n\\\r\n\t\t  radius += RAD_SCALE/radius;\\n\\\r\n\t\t  if(radius>=MAX_BLUR_SIZE)\\n\\\r\n\t\t\t return color / tot;\\n\\\r\n\t\t }\\n\\\r\n\t\t return color / tot;\\n\\\r\n\t\t}\\n\\\r\n\t\tvoid main()\\n\\\r\n\t\t{\\n\\\r\n\t\t\tgl_FragColor = vec4( depthOfField( v_coord, u_focus_point, u_focus_scale ), 1.0 );\\n\\\r\n\t\t\t//gl_FragColor = vec4( texture2D(u_depth_texture, v_coord).r );\\n\\\r\n\t\t}\\n\\\r\n\t\t\";\r\n\r\n\tLiteGraph.registerNodeType(\"fx/DOF\", LGraphDepthOfField );\r\n\tglobal.LGraphDepthOfField = LGraphDepthOfField;\r\n\t*/\r\n\r\n        //*******************************************************\r\n\r\n        function LGraphFXBokeh() {\r\n            this.addInput(\"Texture\", \"Texture\");\r\n            this.addInput(\"Blurred\", \"Texture\");\r\n            this.addInput(\"Mask\", \"Texture\");\r\n            this.addInput(\"Threshold\", \"number\");\r\n            this.addOutput(\"Texture\", \"Texture\");\r\n            this.properties = {\r\n                shape: \"\",\r\n                size: 10,\r\n                alpha: 1.0,\r\n                threshold: 1.0,\r\n                high_precision: false\r\n            };\r\n        }\r\n\r\n        LGraphFXBokeh.title = \"Bokeh\";\r\n        LGraphFXBokeh.desc = \"applies an Bokeh effect\";\r\n\r\n        LGraphFXBokeh.widgets_info = { shape: { widget: \"texture\" } };\r\n\r\n        LGraphFXBokeh.prototype.onExecute = function() {\r\n            var tex = this.getInputData(0);\r\n            var blurred_tex = this.getInputData(1);\r\n            var mask_tex = this.getInputData(2);\r\n            if (!tex || !mask_tex || !this.properties.shape) {\r\n                this.setOutputData(0, tex);\r\n                return;\r\n            }\r\n\r\n            if (!blurred_tex) {\r\n                blurred_tex = tex;\r\n            }\r\n\r\n            var shape_tex = LGraphTexture.getTexture(this.properties.shape);\r\n            if (!shape_tex) {\r\n                return;\r\n            }\r\n\r\n            var threshold = this.properties.threshold;\r\n            if (this.isInputConnected(3)) {\r\n                threshold = this.getInputData(3);\r\n                this.properties.threshold = threshold;\r\n            }\r\n\r\n            var precision = gl.UNSIGNED_BYTE;\r\n            if (this.properties.high_precision) {\r\n                precision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT;\r\n            }\r\n            if (\r\n                !this._temp_texture ||\r\n                this._temp_texture.type != precision ||\r\n                this._temp_texture.width != tex.width ||\r\n                this._temp_texture.height != tex.height\r\n            ) {\r\n                this._temp_texture = new GL.Texture(tex.width, tex.height, {\r\n                    type: precision,\r\n                    format: gl.RGBA,\r\n                    filter: gl.LINEAR\r\n                });\r\n            }\r\n\r\n            //iterations\r\n            var size = this.properties.size;\r\n\r\n            var first_shader = LGraphFXBokeh._first_shader;\r\n            if (!first_shader) {\r\n                first_shader = LGraphFXBokeh._first_shader = new GL.Shader(\r\n                    Shader.SCREEN_VERTEX_SHADER,\r\n                    LGraphFXBokeh._first_pixel_shader\r\n                );\r\n            }\r\n\r\n            var second_shader = LGraphFXBokeh._second_shader;\r\n            if (!second_shader) {\r\n                second_shader = LGraphFXBokeh._second_shader = new GL.Shader(\r\n                    LGraphFXBokeh._second_vertex_shader,\r\n                    LGraphFXBokeh._second_pixel_shader\r\n                );\r\n            }\r\n\r\n            var points_mesh = this._points_mesh;\r\n            if (\r\n                !points_mesh ||\r\n                points_mesh._width != tex.width ||\r\n                points_mesh._height != tex.height ||\r\n                points_mesh._spacing != 2\r\n            ) {\r\n                points_mesh = this.createPointsMesh(tex.width, tex.height, 2);\r\n            }\r\n\r\n            var screen_mesh = Mesh.getScreenQuad();\r\n\r\n            var point_size = this.properties.size;\r\n            var min_light = this.properties.min_light;\r\n            var alpha = this.properties.alpha;\r\n\r\n            gl.disable(gl.DEPTH_TEST);\r\n            gl.disable(gl.BLEND);\r\n\r\n            this._temp_texture.drawTo(function() {\r\n                tex.bind(0);\r\n                blurred_tex.bind(1);\r\n                mask_tex.bind(2);\r\n                first_shader\r\n                    .uniforms({\r\n                        u_texture: 0,\r\n                        u_texture_blur: 1,\r\n                        u_mask: 2,\r\n                        u_texsize: [tex.width, tex.height]\r\n                    })\r\n                    .draw(screen_mesh);\r\n            });\r\n\r\n            this._temp_texture.drawTo(function() {\r\n                //clear because we use blending\r\n                //gl.clearColor(0.0,0.0,0.0,1.0);\r\n                //gl.clear( gl.COLOR_BUFFER_BIT );\r\n                gl.enable(gl.BLEND);\r\n                gl.blendFunc(gl.ONE, gl.ONE);\r\n\r\n                tex.bind(0);\r\n                shape_tex.bind(3);\r\n                second_shader\r\n                    .uniforms({\r\n                        u_texture: 0,\r\n                        u_mask: 2,\r\n                        u_shape: 3,\r\n                        u_alpha: alpha,\r\n                        u_threshold: threshold,\r\n                        u_pointSize: point_size,\r\n                        u_itexsize: [1.0 / tex.width, 1.0 / tex.height]\r\n                    })\r\n                    .draw(points_mesh, gl.POINTS);\r\n            });\r\n\r\n            this.setOutputData(0, this._temp_texture);\r\n        };\r\n\r\n        LGraphFXBokeh.prototype.createPointsMesh = function(\r\n            width,\r\n            height,\r\n            spacing\r\n        ) {\r\n            var nwidth = Math.round(width / spacing);\r\n            var nheight = Math.round(height / spacing);\r\n\r\n            var vertices = new Float32Array(nwidth * nheight * 2);\r\n\r\n            var ny = -1;\r\n            var dx = (2 / width) * spacing;\r\n            var dy = (2 / height) * spacing;\r\n            for (var y = 0; y < nheight; ++y) {\r\n                var nx = -1;\r\n                for (var x = 0; x < nwidth; ++x) {\r\n                    var pos = y * nwidth * 2 + x * 2;\r\n                    vertices[pos] = nx;\r\n                    vertices[pos + 1] = ny;\r\n                    nx += dx;\r\n                }\r\n                ny += dy;\r\n            }\r\n\r\n            this._points_mesh = GL.Mesh.load({ vertices2D: vertices });\r\n            this._points_mesh._width = width;\r\n            this._points_mesh._height = height;\r\n            this._points_mesh._spacing = spacing;\r\n\r\n            return this._points_mesh;\r\n        };\r\n\r\n        /*\r\n\tLGraphTextureBokeh._pixel_shader = \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 a_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform sampler2D u_shape;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D( u_texture, gl_PointCoord );\\n\\\r\n\t\t\t\tcolor *= v_color * u_alpha;\\n\\\r\n\t\t\t\tgl_FragColor = color;\\n\\\r\n\t\t\t}\\n\";\r\n\t*/\r\n\r\n        LGraphFXBokeh._first_pixel_shader =\r\n            \"precision highp float;\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform sampler2D u_texture_blur;\\n\\\r\n\t\t\tuniform sampler2D u_mask;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tvec4 blurred_color = texture2D(u_texture_blur, v_coord);\\n\\\r\n\t\t\t\tfloat mask = texture2D(u_mask, v_coord).x;\\n\\\r\n\t\t\t   gl_FragColor = mix(color, blurred_color, mask);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\n        LGraphFXBokeh._second_vertex_shader =\r\n            \"precision highp float;\\n\\\r\n\t\t\tattribute vec2 a_vertex2D;\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform sampler2D u_mask;\\n\\\r\n\t\t\tuniform vec2 u_itexsize;\\n\\\r\n\t\t\tuniform float u_pointSize;\\n\\\r\n\t\t\tuniform float u_threshold;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec2 coord = a_vertex2D * 0.5 + 0.5;\\n\\\r\n\t\t\t\tv_color = texture2D( u_texture, coord );\\n\\\r\n\t\t\t\tv_color += texture2D( u_texture, coord + vec2(u_itexsize.x, 0.0) );\\n\\\r\n\t\t\t\tv_color += texture2D( u_texture, coord + vec2(0.0, u_itexsize.y));\\n\\\r\n\t\t\t\tv_color += texture2D( u_texture, coord + u_itexsize);\\n\\\r\n\t\t\t\tv_color *= 0.25;\\n\\\r\n\t\t\t\tfloat mask = texture2D(u_mask, coord).x;\\n\\\r\n\t\t\t\tfloat luminance = length(v_color) * mask;\\n\\\r\n\t\t\t\t/*luminance /= (u_pointSize*u_pointSize)*0.01 */;\\n\\\r\n\t\t\t\tluminance -= u_threshold;\\n\\\r\n\t\t\t\tif(luminance < 0.0)\\n\\\r\n\t\t\t\t{\\n\\\r\n\t\t\t\t\tgl_Position.x = -100.0;\\n\\\r\n\t\t\t\t\treturn;\\n\\\r\n\t\t\t\t}\\n\\\r\n\t\t\t\tgl_PointSize = u_pointSize;\\n\\\r\n\t\t\t\tgl_Position = vec4(a_vertex2D,0.0,1.0);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\n        LGraphFXBokeh._second_pixel_shader =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t\tuniform sampler2D u_shape;\\n\\\r\n\t\t\tuniform float u_alpha;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D( u_shape, gl_PointCoord );\\n\\\r\n\t\t\t\tcolor *= v_color * u_alpha;\\n\\\r\n\t\t\t\tgl_FragColor = color;\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LiteGraph.registerNodeType(\"fx/bokeh\", LGraphFXBokeh);\r\n        global.LGraphFXBokeh = LGraphFXBokeh;\r\n\r\n        //************************************************\r\n\r\n        function LGraphFXGeneric() {\r\n            this.addInput(\"Texture\", \"Texture\");\r\n            this.addInput(\"value1\", \"number\");\r\n            this.addInput(\"value2\", \"number\");\r\n            this.addOutput(\"Texture\", \"Texture\");\r\n            this.properties = {\r\n                fx: \"halftone\",\r\n                value1: 1,\r\n                value2: 1,\r\n                precision: LGraphTexture.DEFAULT\r\n            };\r\n        }\r\n\r\n        LGraphFXGeneric.title = \"FX\";\r\n        LGraphFXGeneric.desc = \"applies an FX from a list\";\r\n\r\n        LGraphFXGeneric.widgets_info = {\r\n            fx: {\r\n                widget: \"combo\",\r\n                values: [\"halftone\", \"pixelate\", \"lowpalette\", \"noise\", \"gamma\"]\r\n            },\r\n            precision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\r\n        };\r\n        LGraphFXGeneric.shaders = {};\r\n\r\n        LGraphFXGeneric.prototype.onExecute = function() {\r\n            if (!this.isOutputConnected(0)) {\r\n                return;\r\n            } //saves work\r\n\r\n            var tex = this.getInputData(0);\r\n            if (this.properties.precision === LGraphTexture.PASS_THROUGH) {\r\n                this.setOutputData(0, tex);\r\n                return;\r\n            }\r\n\r\n            if (!tex) {\r\n                return;\r\n            }\r\n\r\n            this._tex = LGraphTexture.getTargetTexture(\r\n                tex,\r\n                this._tex,\r\n                this.properties.precision\r\n            );\r\n\r\n            //iterations\r\n            var value1 = this.properties.value1;\r\n            if (this.isInputConnected(1)) {\r\n                value1 = this.getInputData(1);\r\n                this.properties.value1 = value1;\r\n            }\r\n\r\n            var value2 = this.properties.value2;\r\n            if (this.isInputConnected(2)) {\r\n                value2 = this.getInputData(2);\r\n                this.properties.value2 = value2;\r\n            }\r\n\r\n            var fx = this.properties.fx;\r\n            var shader = LGraphFXGeneric.shaders[fx];\r\n            if (!shader) {\r\n                var pixel_shader_code = LGraphFXGeneric[\"pixel_shader_\" + fx];\r\n                if (!pixel_shader_code) {\r\n                    return;\r\n                }\r\n\r\n                shader = LGraphFXGeneric.shaders[fx] = new GL.Shader(\r\n                    Shader.SCREEN_VERTEX_SHADER,\r\n                    pixel_shader_code\r\n                );\r\n            }\r\n\r\n            gl.disable(gl.BLEND);\r\n            gl.disable(gl.DEPTH_TEST);\r\n            var mesh = Mesh.getScreenQuad();\r\n            var camera = global.LS ? LS.Renderer._current_camera : null;\r\n            var camera_planes;\r\n            if (camera) {\r\n                camera_planes = [\r\n                    LS.Renderer._current_camera.near,\r\n                    LS.Renderer._current_camera.far\r\n                ];\r\n            } else {\r\n                camera_planes = [1, 100];\r\n            }\r\n\r\n            var noise = null;\r\n            if (fx == \"noise\") {\r\n                noise = LGraphTexture.getNoiseTexture();\r\n            }\r\n\r\n            this._tex.drawTo(function() {\r\n                tex.bind(0);\r\n                if (fx == \"noise\") {\r\n                    noise.bind(1);\r\n                }\r\n\r\n                shader\r\n                    .uniforms({\r\n                        u_texture: 0,\r\n                        u_noise: 1,\r\n                        u_size: [tex.width, tex.height],\r\n                        u_rand: [Math.random(), Math.random()],\r\n                        u_value1: value1,\r\n                        u_value2: value2,\r\n                        u_camera_planes: camera_planes\r\n                    })\r\n                    .draw(mesh);\r\n            });\r\n\r\n            this.setOutputData(0, this._tex);\r\n        };\r\n\r\n        LGraphFXGeneric.pixel_shader_halftone =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec2 u_camera_planes;\\n\\\r\n\t\t\tuniform vec2 u_size;\\n\\\r\n\t\t\tuniform float u_value1;\\n\\\r\n\t\t\tuniform float u_value2;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tfloat pattern() {\\n\\\r\n\t\t\t\tfloat s = sin(u_value1 * 3.1415), c = cos(u_value1 * 3.1415);\\n\\\r\n\t\t\t\tvec2 tex = v_coord * u_size.xy;\\n\\\r\n\t\t\t\tvec2 point = vec2(\\n\\\r\n\t\t\t\t   c * tex.x - s * tex.y ,\\n\\\r\n\t\t\t\t   s * tex.x + c * tex.y \\n\\\r\n\t\t\t\t) * u_value2;\\n\\\r\n\t\t\t\treturn (sin(point.x) * sin(point.y)) * 4.0;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tfloat average = (color.r + color.g + color.b) / 3.0;\\n\\\r\n\t\t\t\tgl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LGraphFXGeneric.pixel_shader_pixelate =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec2 u_camera_planes;\\n\\\r\n\t\t\tuniform vec2 u_size;\\n\\\r\n\t\t\tuniform float u_value1;\\n\\\r\n\t\t\tuniform float u_value2;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec2 coord = vec2( floor(v_coord.x * u_value1) / u_value1, floor(v_coord.y * u_value2) / u_value2 );\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, coord);\\n\\\r\n\t\t\t\tgl_FragColor = color;\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LGraphFXGeneric.pixel_shader_lowpalette =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec2 u_camera_planes;\\n\\\r\n\t\t\tuniform vec2 u_size;\\n\\\r\n\t\t\tuniform float u_value1;\\n\\\r\n\t\t\tuniform float u_value2;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tgl_FragColor = floor(color * u_value1) / u_value1;\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LGraphFXGeneric.pixel_shader_noise =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform sampler2D u_noise;\\n\\\r\n\t\t\tuniform vec2 u_size;\\n\\\r\n\t\t\tuniform float u_value1;\\n\\\r\n\t\t\tuniform float u_value2;\\n\\\r\n\t\t\tuniform vec2 u_rand;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tvec3 noise = texture2D(u_noise, v_coord * vec2(u_size.x / 512.0, u_size.y / 512.0) + u_rand).xyz - vec3(0.5);\\n\\\r\n\t\t\t\tgl_FragColor = vec4( color.xyz + noise * u_value1, color.a );\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LGraphFXGeneric.pixel_shader_gamma =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform float u_value1;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tfloat gamma = 1.0 / u_value1;\\n\\\r\n\t\t\t\tgl_FragColor = vec4( pow( color.xyz, vec3(gamma) ), color.a );\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LiteGraph.registerNodeType(\"fx/generic\", LGraphFXGeneric);\r\n        global.LGraphFXGeneric = LGraphFXGeneric;\r\n\r\n        // Vigneting ************************************\r\n\r\n        function LGraphFXVigneting() {\r\n            this.addInput(\"Tex.\", \"Texture\");\r\n            this.addInput(\"intensity\", \"number\");\r\n\r\n            this.addOutput(\"Texture\", \"Texture\");\r\n            this.properties = {\r\n                intensity: 1,\r\n                invert: false,\r\n                precision: LGraphTexture.DEFAULT\r\n            };\r\n\r\n            if (!LGraphFXVigneting._shader) {\r\n                LGraphFXVigneting._shader = new GL.Shader(\r\n                    Shader.SCREEN_VERTEX_SHADER,\r\n                    LGraphFXVigneting.pixel_shader\r\n                );\r\n            }\r\n        }\r\n\r\n        LGraphFXVigneting.title = \"Vigneting\";\r\n        LGraphFXVigneting.desc = \"Vigneting\";\r\n\r\n        LGraphFXVigneting.widgets_info = {\r\n            precision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\r\n        };\r\n\r\n        LGraphFXVigneting.prototype.onExecute = function() {\r\n            var tex = this.getInputData(0);\r\n\r\n            if (this.properties.precision === LGraphTexture.PASS_THROUGH) {\r\n                this.setOutputData(0, tex);\r\n                return;\r\n            }\r\n\r\n            if (!tex) {\r\n                return;\r\n            }\r\n\r\n            this._tex = LGraphTexture.getTargetTexture(\r\n                tex,\r\n                this._tex,\r\n                this.properties.precision\r\n            );\r\n\r\n            var intensity = this.properties.intensity;\r\n            if (this.isInputConnected(1)) {\r\n                intensity = this.getInputData(1);\r\n                this.properties.intensity = intensity;\r\n            }\r\n\r\n            gl.disable(gl.BLEND);\r\n            gl.disable(gl.DEPTH_TEST);\r\n\r\n            var mesh = Mesh.getScreenQuad();\r\n            var shader = LGraphFXVigneting._shader;\r\n            var invert = this.properties.invert;\r\n\r\n            this._tex.drawTo(function() {\r\n                tex.bind(0);\r\n                shader\r\n                    .uniforms({\r\n                        u_texture: 0,\r\n                        u_intensity: intensity,\r\n                        u_isize: [1 / tex.width, 1 / tex.height],\r\n                        u_invert: invert ? 1 : 0\r\n                    })\r\n                    .draw(mesh);\r\n            });\r\n\r\n            this.setOutputData(0, this._tex);\r\n        };\r\n\r\n        LGraphFXVigneting.pixel_shader =\r\n            \"precision highp float;\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform float u_intensity;\\n\\\r\n\t\t\tuniform int u_invert;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tfloat luminance = 1.0 - length( v_coord - vec2(0.5) ) * 1.414;\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tif(u_invert == 1)\\n\\\r\n\t\t\t\t\tluminance = 1.0 - luminance;\\n\\\r\n\t\t\t\tluminance = mix(1.0, luminance, u_intensity);\\n\\\r\n\t\t\t   gl_FragColor = vec4( luminance * color.xyz, color.a);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\n        LiteGraph.registerNodeType(\"fx/vigneting\", LGraphFXVigneting);\r\n        global.LGraphFXVigneting = LGraphFXVigneting;\r\n    }\r\n})(this);\r\n\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n    var MIDI_COLOR = \"#243\";\r\n\r\n    function MIDIEvent(data) {\r\n        this.channel = 0;\r\n        this.cmd = 0;\r\n        this.data = new Uint32Array(3);\r\n\r\n        if (data) {\r\n            this.setup(data);\r\n        }\r\n    }\r\n\r\n    LiteGraph.MIDIEvent = MIDIEvent;\r\n\r\n    MIDIEvent.prototype.fromJSON = function(o) {\r\n        this.setup(o.data);\r\n    };\r\n\r\n    MIDIEvent.prototype.setup = function(data) {\r\n        var raw_data = data;\r\n        if (data.constructor === Object) {\r\n            raw_data = data.data;\r\n        }\r\n\r\n        this.data.set(raw_data);\r\n\r\n        var midiStatus = raw_data[0];\r\n        this.status = midiStatus;\r\n\r\n        var midiCommand = midiStatus & 0xf0;\r\n\r\n        if (midiStatus >= 0xf0) {\r\n            this.cmd = midiStatus;\r\n        } else {\r\n            this.cmd = midiCommand;\r\n        }\r\n\r\n        if (this.cmd == MIDIEvent.NOTEON && this.velocity == 0) {\r\n            this.cmd = MIDIEvent.NOTEOFF;\r\n        }\r\n\r\n        this.cmd_str = MIDIEvent.commands[this.cmd] || \"\";\r\n\r\n        if (\r\n            midiCommand >= MIDIEvent.NOTEON ||\r\n            midiCommand <= MIDIEvent.NOTEOFF\r\n        ) {\r\n            this.channel = midiStatus & 0x0f;\r\n        }\r\n    };\r\n\r\n    Object.defineProperty(MIDIEvent.prototype, \"velocity\", {\r\n        get: function() {\r\n            if (this.cmd == MIDIEvent.NOTEON) {\r\n                return this.data[2];\r\n            }\r\n            return -1;\r\n        },\r\n        set: function(v) {\r\n            this.data[2] = v; //  v / 127;\r\n        },\r\n        enumerable: true\r\n    });\r\n\r\n    MIDIEvent.notes = [\r\n        \"A\",\r\n        \"A#\",\r\n        \"B\",\r\n        \"C\",\r\n        \"C#\",\r\n        \"D\",\r\n        \"D#\",\r\n        \"E\",\r\n        \"F\",\r\n        \"F#\",\r\n        \"G\",\r\n        \"G#\"\r\n    ];\r\n    MIDIEvent.note_to_index = {\r\n        A: 0,\r\n        \"A#\": 1,\r\n        B: 2,\r\n        C: 3,\r\n        \"C#\": 4,\r\n        D: 5,\r\n        \"D#\": 6,\r\n        E: 7,\r\n        F: 8,\r\n        \"F#\": 9,\r\n        G: 10,\r\n        \"G#\": 11\r\n    };\r\n\r\n    Object.defineProperty(MIDIEvent.prototype, \"note\", {\r\n        get: function() {\r\n            if (this.cmd != MIDIEvent.NOTEON) {\r\n                return -1;\r\n            }\r\n            return MIDIEvent.toNoteString(this.data[1], true);\r\n        },\r\n        set: function(v) {\r\n            throw \"notes cannot be assigned this way, must modify the data[1]\";\r\n        },\r\n        enumerable: true\r\n    });\r\n\r\n    Object.defineProperty(MIDIEvent.prototype, \"octave\", {\r\n        get: function() {\r\n            if (this.cmd != MIDIEvent.NOTEON) {\r\n                return -1;\r\n            }\r\n            var octave = this.data[1] - 24;\r\n            return Math.floor(octave / 12 + 1);\r\n        },\r\n        set: function(v) {\r\n            throw \"octave cannot be assigned this way, must modify the data[1]\";\r\n        },\r\n        enumerable: true\r\n    });\r\n\r\n    //returns HZs\r\n    MIDIEvent.prototype.getPitch = function() {\r\n        return Math.pow(2, (this.data[1] - 69) / 12) * 440;\r\n    };\r\n\r\n    MIDIEvent.computePitch = function(note) {\r\n        return Math.pow(2, (note - 69) / 12) * 440;\r\n    };\r\n\r\n    MIDIEvent.prototype.getCC = function() {\r\n        return this.data[1];\r\n    };\r\n\r\n    MIDIEvent.prototype.getCCValue = function() {\r\n        return this.data[2];\r\n    };\r\n\r\n    //not tested, there is a formula missing here\r\n    MIDIEvent.prototype.getPitchBend = function() {\r\n        return this.data[1] + (this.data[2] << 7) - 8192;\r\n    };\r\n\r\n    MIDIEvent.computePitchBend = function(v1, v2) {\r\n        return v1 + (v2 << 7) - 8192;\r\n    };\r\n\r\n    MIDIEvent.prototype.setCommandFromString = function(str) {\r\n        this.cmd = MIDIEvent.computeCommandFromString(str);\r\n    };\r\n\r\n    MIDIEvent.computeCommandFromString = function(str) {\r\n        if (!str) {\r\n            return 0;\r\n        }\r\n\r\n        if (str && str.constructor === Number) {\r\n            return str;\r\n        }\r\n\r\n        str = str.toUpperCase();\r\n        switch (str) {\r\n            case \"NOTE ON\":\r\n            case \"NOTEON\":\r\n                return MIDIEvent.NOTEON;\r\n                break;\r\n            case \"NOTE OFF\":\r\n            case \"NOTEOFF\":\r\n                return MIDIEvent.NOTEON;\r\n                break;\r\n            case \"KEY PRESSURE\":\r\n            case \"KEYPRESSURE\":\r\n                return MIDIEvent.KEYPRESSURE;\r\n                break;\r\n            case \"CONTROLLER CHANGE\":\r\n            case \"CONTROLLERCHANGE\":\r\n            case \"CC\":\r\n                return MIDIEvent.CONTROLLERCHANGE;\r\n                break;\r\n            case \"PROGRAM CHANGE\":\r\n            case \"PROGRAMCHANGE\":\r\n            case \"PC\":\r\n                return MIDIEvent.PROGRAMCHANGE;\r\n                break;\r\n            case \"CHANNEL PRESSURE\":\r\n            case \"CHANNELPRESSURE\":\r\n                return MIDIEvent.CHANNELPRESSURE;\r\n                break;\r\n            case \"PITCH BEND\":\r\n            case \"PITCHBEND\":\r\n                return MIDIEvent.PITCHBEND;\r\n                break;\r\n            case \"TIME TICK\":\r\n            case \"TIMETICK\":\r\n                return MIDIEvent.TIMETICK;\r\n                break;\r\n            default:\r\n                return Number(str); //assume its a hex code\r\n        }\r\n    };\r\n\r\n    //transform from a pitch number to string like \"C4\"\r\n    MIDIEvent.toNoteString = function(d, skip_octave) {\r\n        d = Math.round(d); //in case it has decimals\r\n        var note = d - 21;\r\n        var octave = Math.floor((d - 24) / 12 + 1);\r\n        note = note % 12;\r\n        if (note < 0) {\r\n            note = 12 + note;\r\n        }\r\n        return MIDIEvent.notes[note] + (skip_octave ? \"\" : octave);\r\n    };\r\n\r\n    MIDIEvent.NoteStringToPitch = function(str) {\r\n        str = str.toUpperCase();\r\n        var note = str[0];\r\n        var octave = 4;\r\n\r\n        if (str[1] == \"#\") {\r\n            note += \"#\";\r\n            if (str.length > 2) {\r\n                octave = Number(str[2]);\r\n            }\r\n        } else {\r\n            if (str.length > 1) {\r\n                octave = Number(str[1]);\r\n            }\r\n        }\r\n        var pitch = MIDIEvent.note_to_index[note];\r\n        if (pitch == null) {\r\n            return null;\r\n        }\r\n        return (octave - 1) * 12 + pitch + 21;\r\n    };\r\n\r\n    MIDIEvent.prototype.toString = function() {\r\n        var str = \"\" + this.channel + \". \";\r\n        switch (this.cmd) {\r\n            case MIDIEvent.NOTEON:\r\n                str += \"NOTEON \" + MIDIEvent.toNoteString(this.data[1]);\r\n                break;\r\n            case MIDIEvent.NOTEOFF:\r\n                str += \"NOTEOFF \" + MIDIEvent.toNoteString(this.data[1]);\r\n                break;\r\n            case MIDIEvent.CONTROLLERCHANGE:\r\n                str += \"CC \" + this.data[1] + \" \" + this.data[2];\r\n                break;\r\n            case MIDIEvent.PROGRAMCHANGE:\r\n                str += \"PC \" + this.data[1];\r\n                break;\r\n            case MIDIEvent.PITCHBEND:\r\n                str += \"PITCHBEND \" + this.getPitchBend();\r\n                break;\r\n            case MIDIEvent.KEYPRESSURE:\r\n                str += \"KEYPRESS \" + this.data[1];\r\n                break;\r\n        }\r\n\r\n        return str;\r\n    };\r\n\r\n    MIDIEvent.prototype.toHexString = function() {\r\n        var str = \"\";\r\n        for (var i = 0; i < this.data.length; i++) {\r\n            str += this.data[i].toString(16) + \" \";\r\n        }\r\n    };\r\n\r\n    MIDIEvent.prototype.toJSON = function() {\r\n        return {\r\n            data: [this.data[0], this.data[1], this.data[2]],\r\n            object_class: \"MIDIEvent\"\r\n        };\r\n    };\r\n\r\n    MIDIEvent.NOTEOFF = 0x80;\r\n    MIDIEvent.NOTEON = 0x90;\r\n    MIDIEvent.KEYPRESSURE = 0xa0;\r\n    MIDIEvent.CONTROLLERCHANGE = 0xb0;\r\n    MIDIEvent.PROGRAMCHANGE = 0xc0;\r\n    MIDIEvent.CHANNELPRESSURE = 0xd0;\r\n    MIDIEvent.PITCHBEND = 0xe0;\r\n    MIDIEvent.TIMETICK = 0xf8;\r\n\r\n    MIDIEvent.commands = {\r\n        0x80: \"note off\",\r\n        0x90: \"note on\",\r\n        0xa0: \"key pressure\",\r\n        0xb0: \"controller change\",\r\n        0xc0: \"program change\",\r\n        0xd0: \"channel pressure\",\r\n        0xe0: \"pitch bend\",\r\n        0xf0: \"system\",\r\n        0xf2: \"Song pos\",\r\n        0xf3: \"Song select\",\r\n        0xf6: \"Tune request\",\r\n        0xf8: \"time tick\",\r\n        0xfa: \"Start Song\",\r\n        0xfb: \"Continue Song\",\r\n        0xfc: \"Stop Song\",\r\n        0xfe: \"Sensing\",\r\n        0xff: \"Reset\"\r\n    };\r\n\r\n    MIDIEvent.commands_short = {\r\n        0x80: \"NOTEOFF\",\r\n        0x90: \"NOTEOFF\",\r\n        0xa0: \"KEYP\",\r\n        0xb0: \"CC\",\r\n        0xc0: \"PC\",\r\n        0xd0: \"CP\",\r\n        0xe0: \"PB\",\r\n        0xf0: \"SYS\",\r\n        0xf2: \"POS\",\r\n        0xf3: \"SELECT\",\r\n        0xf6: \"TUNEREQ\",\r\n        0xf8: \"TT\",\r\n        0xfa: \"START\",\r\n        0xfb: \"CONTINUE\",\r\n        0xfc: \"STOP\",\r\n        0xfe: \"SENS\",\r\n        0xff: \"RESET\"\r\n    };\r\n\r\n    MIDIEvent.commands_reversed = {};\r\n    for (var i in MIDIEvent.commands) {\r\n        MIDIEvent.commands_reversed[MIDIEvent.commands[i]] = i;\r\n    }\r\n\r\n    //MIDI wrapper, instantiate by MIDIIn and MIDIOut\r\n    function MIDIInterface(on_ready, on_error) {\r\n        if (!navigator.requestMIDIAccess) {\r\n            this.error = \"not suppoorted\";\r\n            if (on_error) {\r\n                on_error(\"Not supported\");\r\n            } else {\r\n                console.error(\"MIDI NOT SUPPORTED, enable by chrome://flags\");\r\n            }\r\n            return;\r\n        }\r\n\r\n        this.on_ready = on_ready;\r\n\r\n        this.state = {\r\n            note: [],\r\n            cc: []\r\n        };\r\n\r\n\t\tthis.input_ports = null;\r\n\t\tthis.input_ports_info = [];\r\n\t\tthis.output_ports = null;\r\n\t\tthis.output_ports_info = [];\r\n\r\n        navigator.requestMIDIAccess().then(this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this));\r\n    }\r\n\r\n    MIDIInterface.input = null;\r\n\r\n    MIDIInterface.MIDIEvent = MIDIEvent;\r\n\r\n    MIDIInterface.prototype.onMIDISuccess = function(midiAccess) {\r\n        console.log(\"MIDI ready!\");\r\n        console.log(midiAccess);\r\n        this.midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance)\r\n        this.updatePorts();\r\n\r\n        if (this.on_ready) {\r\n            this.on_ready(this);\r\n        }\r\n    };\r\n\r\n    MIDIInterface.prototype.updatePorts = function() {\r\n        var midi = this.midi;\r\n        this.input_ports = midi.inputs;\r\n\t\tthis.input_ports_info = [];\r\n        this.output_ports = midi.outputs;\r\n\t\tthis.output_ports_info = [];\r\n\r\n        var num = 0;\r\n\r\n        var it = this.input_ports.values();\r\n        var it_value = it.next();\r\n        while (it_value && it_value.done === false) {\r\n            var port_info = it_value.value;\r\n\t\t\tthis.input_ports_info.push(port_info);\r\n            console.log( \"Input port [type:'\" + port_info.type + \"'] id:'\" + port_info.id + \"' manufacturer:'\" + port_info.manufacturer + \"' name:'\" + port_info.name + \"' version:'\" + port_info.version + \"'\" );\r\n            num++;\r\n            it_value = it.next();\r\n        }\r\n        this.num_input_ports = num;\r\n\r\n        num = 0;\r\n        var it = this.output_ports.values();\r\n        var it_value = it.next();\r\n        while (it_value && it_value.done === false) {\r\n            var port_info = it_value.value;\r\n\t\t\tthis.output_ports_info.push(port_info);\r\n            console.log( \"Output port [type:'\" + port_info.type + \"'] id:'\" + port_info.id + \"' manufacturer:'\" + port_info.manufacturer + \"' name:'\" + port_info.name + \"' version:'\" + port_info.version + \"'\" );\r\n            num++;\r\n            it_value = it.next();\r\n        }\r\n        this.num_output_ports = num;\r\n    };\r\n\r\n    MIDIInterface.prototype.onMIDIFailure = function(msg) {\r\n        console.error(\"Failed to get MIDI access - \" + msg);\r\n    };\r\n\r\n    MIDIInterface.prototype.openInputPort = function(port, callback) {\r\n        var input_port = this.input_ports.get(\"input-\" + port);\r\n        if (!input_port) {\r\n            return false;\r\n        }\r\n        MIDIInterface.input = this;\r\n        var that = this;\r\n\r\n        input_port.onmidimessage = function(a) {\r\n            var midi_event = new MIDIEvent(a.data);\r\n            that.updateState(midi_event);\r\n            if (callback) {\r\n                callback(a.data, midi_event);\r\n            }\r\n            if (MIDIInterface.on_message) {\r\n                MIDIInterface.on_message(a.data, midi_event);\r\n            }\r\n        };\r\n        console.log(\"port open: \", input_port);\r\n        return true;\r\n    };\r\n\r\n    MIDIInterface.parseMsg = function(data) {};\r\n\r\n    MIDIInterface.prototype.updateState = function(midi_event) {\r\n        switch (midi_event.cmd) {\r\n            case MIDIEvent.NOTEON:\r\n                this.state.note[midi_event.value1 | 0] = midi_event.value2;\r\n                break;\r\n            case MIDIEvent.NOTEOFF:\r\n                this.state.note[midi_event.value1 | 0] = 0;\r\n                break;\r\n            case MIDIEvent.CONTROLLERCHANGE:\r\n                this.state.cc[midi_event.getCC()] = midi_event.getCCValue();\r\n                break;\r\n        }\r\n    };\r\n\r\n    MIDIInterface.prototype.sendMIDI = function(port, midi_data) {\r\n        if (!midi_data) {\r\n            return;\r\n        }\r\n\r\n        var output_port = this.output_ports_info[port];//this.output_ports.get(\"output-\" + port);\r\n        if (!output_port) {\r\n            return;\r\n        }\r\n\r\n        MIDIInterface.output = this;\r\n\r\n        if (midi_data.constructor === MIDIEvent) {\r\n            output_port.send(midi_data.data);\r\n        } else {\r\n            output_port.send(midi_data);\r\n        }\r\n    };\r\n\r\n    function LGMIDIIn() {\r\n        this.addOutput(\"on_midi\", LiteGraph.EVENT);\r\n        this.addOutput(\"out\", \"midi\");\r\n        this.properties = { port: 0 };\r\n        this._last_midi_event = null;\r\n        this._current_midi_event = null;\r\n        this.boxcolor = \"#AAA\";\r\n        this._last_time = 0;\r\n\r\n        var that = this;\r\n        new MIDIInterface(function(midi) {\r\n            //open\r\n            that._midi = midi;\r\n            if (that._waiting) {\r\n                that.onStart();\r\n            }\r\n            that._waiting = false;\r\n        });\r\n    }\r\n\r\n    LGMIDIIn.MIDIInterface = MIDIInterface;\r\n\r\n    LGMIDIIn.title = \"MIDI Input\";\r\n    LGMIDIIn.desc = \"Reads MIDI from a input port\";\r\n    LGMIDIIn.color = MIDI_COLOR;\r\n\r\n    LGMIDIIn.prototype.getPropertyInfo = function(name) {\r\n        if (!this._midi) {\r\n            return;\r\n        }\r\n\r\n        if (name == \"port\") {\r\n            var values = {};\r\n            for (var i = 0; i < this._midi.input_ports_info.length; ++i) {\r\n                var input = this._midi.input_ports_info[i];\r\n                values[i] = i + \".- \" + input.name + \" version:\" + input.version;\r\n            }\r\n            return { type: \"enum\", values: values };\r\n        }\r\n    };\r\n\r\n    LGMIDIIn.prototype.onStart = function() {\r\n        if (this._midi) {\r\n            this._midi.openInputPort(\r\n                this.properties.port,\r\n                this.onMIDIEvent.bind(this)\r\n            );\r\n        } else {\r\n            this._waiting = true;\r\n        }\r\n    };\r\n\r\n    LGMIDIIn.prototype.onMIDIEvent = function(data, midi_event) {\r\n        this._last_midi_event = midi_event;\r\n        this.boxcolor = \"#AFA\";\r\n        this._last_time = LiteGraph.getTime();\r\n        this.trigger(\"on_midi\", midi_event);\r\n        if (midi_event.cmd == MIDIEvent.NOTEON) {\r\n            this.trigger(\"on_noteon\", midi_event);\r\n        } else if (midi_event.cmd == MIDIEvent.NOTEOFF) {\r\n            this.trigger(\"on_noteoff\", midi_event);\r\n        } else if (midi_event.cmd == MIDIEvent.CONTROLLERCHANGE) {\r\n            this.trigger(\"on_cc\", midi_event);\r\n        } else if (midi_event.cmd == MIDIEvent.PROGRAMCHANGE) {\r\n            this.trigger(\"on_pc\", midi_event);\r\n        } else if (midi_event.cmd == MIDIEvent.PITCHBEND) {\r\n            this.trigger(\"on_pitchbend\", midi_event);\r\n        }\r\n    };\r\n\r\n    LGMIDIIn.prototype.onDrawBackground = function(ctx) {\r\n        this.boxcolor = \"#AAA\";\r\n        if (!this.flags.collapsed && this._last_midi_event) {\r\n            ctx.fillStyle = \"white\";\r\n            var now = LiteGraph.getTime();\r\n            var f = 1.0 - Math.max(0, (now - this._last_time) * 0.001);\r\n            if (f > 0) {\r\n                var t = ctx.globalAlpha;\r\n                ctx.globalAlpha *= f;\r\n                ctx.font = \"12px Tahoma\";\r\n                ctx.fillText(\r\n                    this._last_midi_event.toString(),\r\n                    2,\r\n                    this.size[1] * 0.5 + 3\r\n                );\r\n                //ctx.fillRect(0,0,this.size[0],this.size[1]);\r\n                ctx.globalAlpha = t;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGMIDIIn.prototype.onExecute = function() {\r\n        if (this.outputs) {\r\n            var last = this._last_midi_event;\r\n            for (var i = 0; i < this.outputs.length; ++i) {\r\n                var output = this.outputs[i];\r\n                var v = null;\r\n                switch (output.name) {\r\n                    case \"midi\":\r\n                        v = this._midi;\r\n                        break;\r\n                    case \"last_midi\":\r\n                        v = last;\r\n                        break;\r\n                    default:\r\n                        continue;\r\n                }\r\n                this.setOutputData(i, v);\r\n            }\r\n        }\r\n    };\r\n\r\n    LGMIDIIn.prototype.onGetOutputs = function() {\r\n        return [\r\n            [\"last_midi\", \"midi\"],\r\n            [\"on_midi\", LiteGraph.EVENT],\r\n            [\"on_noteon\", LiteGraph.EVENT],\r\n            [\"on_noteoff\", LiteGraph.EVENT],\r\n            [\"on_cc\", LiteGraph.EVENT],\r\n            [\"on_pc\", LiteGraph.EVENT],\r\n            [\"on_pitchbend\", LiteGraph.EVENT]\r\n        ];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/input\", LGMIDIIn);\r\n\r\n    function LGMIDIOut() {\r\n        this.addInput(\"send\", LiteGraph.EVENT);\r\n        this.properties = { port: 0 };\r\n\r\n        var that = this;\r\n        new MIDIInterface(function(midi) {\r\n            that._midi = midi;\r\n\t\t\tthat.widget.options.values = that.getMIDIOutputs();\r\n        });\r\n\t\tthis.widget = this.addWidget(\"combo\",\"Device\",this.properties.port,{ property: \"port\", values: this.getMIDIOutputs.bind(this) });\r\n\t\tthis.size = [340,60];\r\n    }\r\n\r\n    LGMIDIOut.MIDIInterface = MIDIInterface;\r\n\r\n    LGMIDIOut.title = \"MIDI Output\";\r\n    LGMIDIOut.desc = \"Sends MIDI to output channel\";\r\n    LGMIDIOut.color = MIDI_COLOR;\r\n\r\n    LGMIDIOut.prototype.onGetPropertyInfo = function(name) {\r\n        if (!this._midi) {\r\n            return;\r\n        }\r\n\r\n        if (name == \"port\") {\r\n\t\t\tvar values = this.getMIDIOutputs();\r\n            return { type: \"enum\", values: values };\r\n        }\r\n    };\r\n\tLGMIDIOut.default_ports = {0:\"unknown\"};\r\n\r\n\tLGMIDIOut.prototype.getMIDIOutputs = function()\r\n\t{\r\n\t\tvar values = {};\r\n\t\tif(!this._midi)\r\n\t\t\treturn LGMIDIOut.default_ports;\r\n\t\tif(this._midi.output_ports_info)\r\n\t\tfor (var i = 0; i < this._midi.output_ports_info.length; ++i) {\r\n\t\t\tvar output = this._midi.output_ports_info[i];\r\n\t\t\tif(!output)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar name = i + \".- \" + output.name + \" version:\" + output.version;\r\n\t\t\tvalues[i] = name;\r\n\t\t}\r\n\t\treturn values;\r\n\t}\r\n\r\n    LGMIDIOut.prototype.onAction = function(event, midi_event) {\r\n        //console.log(midi_event);\r\n        if (!this._midi) {\r\n            return;\r\n        }\r\n        if (event == \"send\") {\r\n            this._midi.sendMIDI(this.properties.port, midi_event);\r\n        }\r\n        this.trigger(\"midi\", midi_event);\r\n    };\r\n\r\n    LGMIDIOut.prototype.onGetInputs = function() {\r\n        return [[\"send\", LiteGraph.ACTION]];\r\n    };\r\n\r\n    LGMIDIOut.prototype.onGetOutputs = function() {\r\n        return [[\"on_midi\", LiteGraph.EVENT]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/output\", LGMIDIOut);\r\n\r\n\r\n    function LGMIDIShow() {\r\n        this.addInput(\"on_midi\", LiteGraph.EVENT);\r\n        this._str = \"\";\r\n        this.size = [200, 40];\r\n    }\r\n\r\n    LGMIDIShow.title = \"MIDI Show\";\r\n    LGMIDIShow.desc = \"Shows MIDI in the graph\";\r\n    LGMIDIShow.color = MIDI_COLOR;\r\n\r\n    LGMIDIShow.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this._str;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    LGMIDIShow.prototype.onAction = function(event, midi_event) {\r\n        if (!midi_event) {\r\n            return;\r\n        }\r\n        if (midi_event.constructor === MIDIEvent) {\r\n            this._str = midi_event.toString();\r\n        } else {\r\n            this._str = \"???\";\r\n        }\r\n    };\r\n\r\n    LGMIDIShow.prototype.onDrawForeground = function(ctx) {\r\n        if (!this._str || this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        ctx.font = \"30px Arial\";\r\n        ctx.fillText(this._str, 10, this.size[1] * 0.8);\r\n    };\r\n\r\n    LGMIDIShow.prototype.onGetInputs = function() {\r\n        return [[\"in\", LiteGraph.ACTION]];\r\n    };\r\n\r\n    LGMIDIShow.prototype.onGetOutputs = function() {\r\n        return [[\"on_midi\", LiteGraph.EVENT]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/show\", LGMIDIShow);\r\n\r\n    function LGMIDIFilter() {\r\n        this.properties = {\r\n            channel: -1,\r\n            cmd: -1,\r\n            min_value: -1,\r\n            max_value: -1\r\n        };\r\n\r\n        var that = this;\r\n        this._learning = false;\r\n        this.addWidget(\"button\", \"Learn\", \"\", function() {\r\n            that._learning = true;\r\n            that.boxcolor = \"#FA3\";\r\n        });\r\n\r\n        this.addInput(\"in\", LiteGraph.EVENT);\r\n        this.addOutput(\"on_midi\", LiteGraph.EVENT);\r\n        this.boxcolor = \"#AAA\";\r\n    }\r\n\r\n    LGMIDIFilter.title = \"MIDI Filter\";\r\n    LGMIDIFilter.desc = \"Filters MIDI messages\";\r\n    LGMIDIFilter.color = MIDI_COLOR;\r\n\r\n    LGMIDIFilter[\"@cmd\"] = {\r\n        type: \"enum\",\r\n        title: \"Command\",\r\n        values: MIDIEvent.commands_reversed\r\n    };\r\n\r\n    LGMIDIFilter.prototype.getTitle = function() {\r\n        var str = null;\r\n        if (this.properties.cmd == -1) {\r\n            str = \"Nothing\";\r\n        } else {\r\n            str = MIDIEvent.commands_short[this.properties.cmd] || \"Unknown\";\r\n        }\r\n\r\n        if (\r\n            this.properties.min_value != -1 &&\r\n            this.properties.max_value != -1\r\n        ) {\r\n            str +=\r\n                \" \" +\r\n                (this.properties.min_value == this.properties.max_value\r\n                    ? this.properties.max_value\r\n                    : this.properties.min_value +\r\n                      \"..\" +\r\n                      this.properties.max_value);\r\n        }\r\n\r\n        return \"Filter: \" + str;\r\n    };\r\n\r\n    LGMIDIFilter.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"cmd\") {\r\n            var num = Number(value);\r\n            if (isNaN(num)) {\r\n                num = MIDIEvent.commands[value] || 0;\r\n            }\r\n            this.properties.cmd = num;\r\n        }\r\n    };\r\n\r\n    LGMIDIFilter.prototype.onAction = function(event, midi_event) {\r\n        if (!midi_event || midi_event.constructor !== MIDIEvent) {\r\n            return;\r\n        }\r\n\r\n        if (this._learning) {\r\n            this._learning = false;\r\n            this.boxcolor = \"#AAA\";\r\n            this.properties.channel = midi_event.channel;\r\n            this.properties.cmd = midi_event.cmd;\r\n            this.properties.min_value = this.properties.max_value =\r\n                midi_event.data[1];\r\n        } else {\r\n            if (\r\n                this.properties.channel != -1 &&\r\n                midi_event.channel != this.properties.channel\r\n            ) {\r\n                return;\r\n            }\r\n            if (\r\n                this.properties.cmd != -1 &&\r\n                midi_event.cmd != this.properties.cmd\r\n            ) {\r\n                return;\r\n            }\r\n            if (\r\n                this.properties.min_value != -1 &&\r\n                midi_event.data[1] < this.properties.min_value\r\n            ) {\r\n                return;\r\n            }\r\n            if (\r\n                this.properties.max_value != -1 &&\r\n                midi_event.data[1] > this.properties.max_value\r\n            ) {\r\n                return;\r\n            }\r\n        }\r\n\r\n        this.trigger(\"on_midi\", midi_event);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/filter\", LGMIDIFilter);\r\n\r\n    function LGMIDIEvent() {\r\n        this.properties = {\r\n            channel: 0,\r\n            cmd: 144, //0x90\r\n            value1: 1,\r\n            value2: 1\r\n        };\r\n\r\n        this.addInput(\"send\", LiteGraph.EVENT);\r\n        this.addInput(\"assign\", LiteGraph.EVENT);\r\n        this.addOutput(\"on_midi\", LiteGraph.EVENT);\r\n\r\n        this.midi_event = new MIDIEvent();\r\n        this.gate = false;\r\n    }\r\n\r\n    LGMIDIEvent.title = \"MIDIEvent\";\r\n    LGMIDIEvent.desc = \"Create a MIDI Event\";\r\n    LGMIDIEvent.color = MIDI_COLOR;\r\n\r\n    LGMIDIEvent.prototype.onAction = function(event, midi_event) {\r\n        if (event == \"assign\") {\r\n            this.properties.channel = midi_event.channel;\r\n            this.properties.cmd = midi_event.cmd;\r\n            this.properties.value1 = midi_event.data[1];\r\n            this.properties.value2 = midi_event.data[2];\r\n            if (midi_event.cmd == MIDIEvent.NOTEON) {\r\n                this.gate = true;\r\n            } else if (midi_event.cmd == MIDIEvent.NOTEOFF) {\r\n                this.gate = false;\r\n            }\r\n            return;\r\n        }\r\n\r\n        //send\r\n        var midi_event = this.midi_event;\r\n        midi_event.channel = this.properties.channel;\r\n        if (this.properties.cmd && this.properties.cmd.constructor === String) {\r\n            midi_event.setCommandFromString(this.properties.cmd);\r\n        } else {\r\n            midi_event.cmd = this.properties.cmd;\r\n        }\r\n        midi_event.data[0] = midi_event.cmd | midi_event.channel;\r\n        midi_event.data[1] = Number(this.properties.value1);\r\n        midi_event.data[2] = Number(this.properties.value2);\r\n\r\n        this.trigger(\"on_midi\", midi_event);\r\n    };\r\n\r\n    LGMIDIEvent.prototype.onExecute = function() {\r\n        var props = this.properties;\r\n\r\n        if (this.inputs) {\r\n            for (var i = 0; i < this.inputs.length; ++i) {\r\n                var input = this.inputs[i];\r\n                if (input.link == -1) {\r\n                    continue;\r\n                }\r\n                switch (input.name) {\r\n                    case \"note\":\r\n                        var v = this.getInputData(i);\r\n                        if (v != null) {\r\n                            if (v.constructor === String) {\r\n                                v = MIDIEvent.NoteStringToPitch(v);\r\n                            }\r\n                            this.properties.value1 = (v | 0) % 255;\r\n                        }\r\n                        break;\r\n                    case \"cmd\":\r\n                        var v = this.getInputData(i);\r\n                        if (v != null) {\r\n                            this.properties.cmd = v;\r\n                        }\r\n                        break;\r\n                    case \"value1\":\r\n                        var v = this.getInputData(i);\r\n                        if (v != null) {\r\n                            this.properties.value1 = clamp(v|0,0,127);\r\n                        }\r\n                        break;\r\n                    case \"value2\":\r\n                        var v = this.getInputData(i);\r\n                        if (v != null) {\r\n                            this.properties.value2 = clamp(v|0,0,127);\r\n                        }\r\n                        break;\r\n                }\r\n            }\r\n        }\r\n\r\n        if (this.outputs) {\r\n            for (var i = 0; i < this.outputs.length; ++i) {\r\n                var output = this.outputs[i];\r\n                var v = null;\r\n                switch (output.name) {\r\n                    case \"midi\":\r\n                        v = new MIDIEvent();\r\n                        v.setup([props.cmd, props.value1, props.value2]);\r\n                        v.channel = props.channel;\r\n                        break;\r\n                    case \"command\":\r\n                        v = props.cmd;\r\n                        break;\r\n                    case \"cc\":\r\n                        v = props.value1;\r\n                        break;\r\n                    case \"cc_value\":\r\n                        v = props.value2;\r\n                        break;\r\n                    case \"note\":\r\n                        v =\r\n                            props.cmd == MIDIEvent.NOTEON ||\r\n                            props.cmd == MIDIEvent.NOTEOFF\r\n                                ? props.value1\r\n                                : null;\r\n                        break;\r\n                    case \"velocity\":\r\n                        v = props.cmd == MIDIEvent.NOTEON ? props.value2 : null;\r\n                        break;\r\n                    case \"pitch\":\r\n                        v =\r\n                            props.cmd == MIDIEvent.NOTEON\r\n                                ? MIDIEvent.computePitch(props.value1)\r\n                                : null;\r\n                        break;\r\n                    case \"pitchbend\":\r\n                        v =\r\n                            props.cmd == MIDIEvent.PITCHBEND\r\n                                ? MIDIEvent.computePitchBend(\r\n                                      props.value1,\r\n                                      props.value2\r\n                                  )\r\n                                : null;\r\n                        break;\r\n                    case \"gate\":\r\n                        v = this.gate;\r\n                        break;\r\n                    default:\r\n                        continue;\r\n                }\r\n                if (v !== null) {\r\n                    this.setOutputData(i, v);\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    LGMIDIEvent.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"cmd\") {\r\n            this.properties.cmd = MIDIEvent.computeCommandFromString(value);\r\n        }\r\n    };\r\n\r\n    LGMIDIEvent.prototype.onGetInputs = function() {\r\n        return [[\"cmd\", \"number\"],[\"note\", \"number\"],[\"value1\", \"number\"],[\"value2\", \"number\"]];\r\n    };\r\n\r\n    LGMIDIEvent.prototype.onGetOutputs = function() {\r\n        return [\r\n            [\"midi\", \"midi\"],\r\n            [\"on_midi\", LiteGraph.EVENT],\r\n            [\"command\", \"number\"],\r\n            [\"note\", \"number\"],\r\n            [\"velocity\", \"number\"],\r\n            [\"cc\", \"number\"],\r\n            [\"cc_value\", \"number\"],\r\n            [\"pitch\", \"number\"],\r\n            [\"gate\", \"bool\"],\r\n            [\"pitchbend\", \"number\"]\r\n        ];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/event\", LGMIDIEvent);\r\n\r\n    function LGMIDICC() {\r\n        this.properties = {\r\n            //\t\tchannel: 0,\r\n            cc: 1,\r\n            value: 0\r\n        };\r\n\r\n        this.addOutput(\"value\", \"number\");\r\n    }\r\n\r\n    LGMIDICC.title = \"MIDICC\";\r\n    LGMIDICC.desc = \"gets a Controller Change\";\r\n    LGMIDICC.color = MIDI_COLOR;\r\n\r\n    LGMIDICC.prototype.onExecute = function() {\r\n        var props = this.properties;\r\n        if (MIDIInterface.input) {\r\n            this.properties.value =\r\n                MIDIInterface.input.state.cc[this.properties.cc];\r\n        }\r\n        this.setOutputData(0, this.properties.value);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/cc\", LGMIDICC);\r\n\r\n    function LGMIDIGenerator() {\r\n        this.addInput(\"generate\", LiteGraph.ACTION);\r\n        this.addInput(\"scale\", \"string\");\r\n        this.addInput(\"octave\", \"number\");\r\n        this.addOutput(\"note\", LiteGraph.EVENT);\r\n        this.properties = {\r\n            notes: \"A,A#,B,C,C#,D,D#,E,F,F#,G,G#\",\r\n            octave: 2,\r\n            duration: 0.5,\r\n            mode: \"sequence\"\r\n        };\r\n\r\n        this.notes_pitches = LGMIDIGenerator.processScale(\r\n            this.properties.notes\r\n        );\r\n        this.sequence_index = 0;\r\n    }\r\n\r\n    LGMIDIGenerator.title = \"MIDI Generator\";\r\n    LGMIDIGenerator.desc = \"Generates a random MIDI note\";\r\n    LGMIDIGenerator.color = MIDI_COLOR;\r\n\r\n    LGMIDIGenerator.processScale = function(scale) {\r\n        var notes = scale.split(\",\");\r\n        for (var i = 0; i < notes.length; ++i) {\r\n            var n = notes[i];\r\n            if ((n.length == 2 && n[1] != \"#\") || n.length > 2) {\r\n                notes[i] = -LiteGraph.MIDIEvent.NoteStringToPitch(n);\r\n            } else {\r\n                notes[i] = MIDIEvent.note_to_index[n] || 0;\r\n            }\r\n        }\r\n        return notes;\r\n    };\r\n\r\n    LGMIDIGenerator.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"notes\") {\r\n            this.notes_pitches = LGMIDIGenerator.processScale(value);\r\n        }\r\n    };\r\n\r\n    LGMIDIGenerator.prototype.onExecute = function() {\r\n        var octave = this.getInputData(2);\r\n        if (octave != null) {\r\n            this.properties.octave = octave;\r\n        }\r\n\r\n        var scale = this.getInputData(1);\r\n        if (scale) {\r\n            this.notes_pitches = LGMIDIGenerator.processScale(scale);\r\n        }\r\n    };\r\n\r\n    LGMIDIGenerator.prototype.onAction = function(event, midi_event) {\r\n        //var range = this.properties.max - this.properties.min;\r\n        //var pitch = this.properties.min + ((Math.random() * range)|0);\r\n        var pitch = 0;\r\n        var range = this.notes_pitches.length;\r\n        var index = 0;\r\n\r\n        if (this.properties.mode == \"sequence\") {\r\n            index = this.sequence_index = (this.sequence_index + 1) % range;\r\n        } else if (this.properties.mode == \"random\") {\r\n            index = Math.floor(Math.random() * range);\r\n        }\r\n\r\n        var note = this.notes_pitches[index];\r\n        if (note >= 0) {\r\n            pitch = note + (this.properties.octave - 1) * 12 + 33;\r\n        } else {\r\n            pitch = -note;\r\n        }\r\n\r\n        var midi_event = new MIDIEvent();\r\n        midi_event.setup([MIDIEvent.NOTEON, pitch, 10]);\r\n        var duration = this.properties.duration || 1;\r\n        this.trigger(\"note\", midi_event);\r\n\r\n        //noteoff\r\n        setTimeout(\r\n            function() {\r\n                var midi_event = new MIDIEvent();\r\n                midi_event.setup([MIDIEvent.NOTEOFF, pitch, 0]);\r\n                this.trigger(\"note\", midi_event);\r\n            }.bind(this),\r\n            duration * 1000\r\n        );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/generator\", LGMIDIGenerator);\r\n\r\n    function LGMIDITranspose() {\r\n        this.properties = {\r\n            amount: 0\r\n        };\r\n        this.addInput(\"in\", LiteGraph.ACTION);\r\n        this.addInput(\"amount\", \"number\");\r\n        this.addOutput(\"out\", LiteGraph.EVENT);\r\n\r\n        this.midi_event = new MIDIEvent();\r\n    }\r\n\r\n    LGMIDITranspose.title = \"MIDI Transpose\";\r\n    LGMIDITranspose.desc = \"Transpose a MIDI note\";\r\n    LGMIDITranspose.color = MIDI_COLOR;\r\n\r\n    LGMIDITranspose.prototype.onAction = function(event, midi_event) {\r\n        if (!midi_event || midi_event.constructor !== MIDIEvent) {\r\n            return;\r\n        }\r\n\r\n        if (\r\n            midi_event.data[0] == MIDIEvent.NOTEON ||\r\n            midi_event.data[0] == MIDIEvent.NOTEOFF\r\n        ) {\r\n            this.midi_event = new MIDIEvent();\r\n            this.midi_event.setup(midi_event.data);\r\n            this.midi_event.data[1] = Math.round(\r\n                this.midi_event.data[1] + this.properties.amount\r\n            );\r\n            this.trigger(\"out\", this.midi_event);\r\n        } else {\r\n            this.trigger(\"out\", midi_event);\r\n        }\r\n    };\r\n\r\n    LGMIDITranspose.prototype.onExecute = function() {\r\n        var amount = this.getInputData(1);\r\n        if (amount != null) {\r\n            this.properties.amount = amount;\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/transpose\", LGMIDITranspose);\r\n\r\n    function LGMIDIQuantize() {\r\n        this.properties = {\r\n            scale: \"A,A#,B,C,C#,D,D#,E,F,F#,G,G#\"\r\n        };\r\n        this.addInput(\"note\", LiteGraph.ACTION);\r\n        this.addInput(\"scale\", \"string\");\r\n        this.addOutput(\"out\", LiteGraph.EVENT);\r\n\r\n        this.valid_notes = new Array(12);\r\n        this.offset_notes = new Array(12);\r\n        this.processScale(this.properties.scale);\r\n    }\r\n\r\n    LGMIDIQuantize.title = \"MIDI Quantize Pitch\";\r\n    LGMIDIQuantize.desc = \"Transpose a MIDI note tp fit an scale\";\r\n    LGMIDIQuantize.color = MIDI_COLOR;\r\n\r\n    LGMIDIQuantize.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"scale\") {\r\n            this.processScale(value);\r\n        }\r\n    };\r\n\r\n    LGMIDIQuantize.prototype.processScale = function(scale) {\r\n        this._current_scale = scale;\r\n        this.notes_pitches = LGMIDIGenerator.processScale(scale);\r\n        for (var i = 0; i < 12; ++i) {\r\n            this.valid_notes[i] = this.notes_pitches.indexOf(i) != -1;\r\n        }\r\n        for (var i = 0; i < 12; ++i) {\r\n            if (this.valid_notes[i]) {\r\n                this.offset_notes[i] = 0;\r\n                continue;\r\n            }\r\n            for (var j = 1; j < 12; ++j) {\r\n                if (this.valid_notes[(i - j) % 12]) {\r\n                    this.offset_notes[i] = -j;\r\n                    break;\r\n                }\r\n                if (this.valid_notes[(i + j) % 12]) {\r\n                    this.offset_notes[i] = j;\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    LGMIDIQuantize.prototype.onAction = function(event, midi_event) {\r\n        if (!midi_event || midi_event.constructor !== MIDIEvent) {\r\n            return;\r\n        }\r\n\r\n        if (\r\n            midi_event.data[0] == MIDIEvent.NOTEON ||\r\n            midi_event.data[0] == MIDIEvent.NOTEOFF\r\n        ) {\r\n            this.midi_event = new MIDIEvent();\r\n            this.midi_event.setup(midi_event.data);\r\n            var note = midi_event.note;\r\n            var index = MIDIEvent.note_to_index[note];\r\n            var offset = this.offset_notes[index];\r\n            this.midi_event.data[1] += offset;\r\n            this.trigger(\"out\", this.midi_event);\r\n        } else {\r\n            this.trigger(\"out\", midi_event);\r\n        }\r\n    };\r\n\r\n    LGMIDIQuantize.prototype.onExecute = function() {\r\n        var scale = this.getInputData(1);\r\n        if (scale != null && scale != this._current_scale) {\r\n            this.processScale(scale);\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/quantize\", LGMIDIQuantize);\r\n\r\n\tfunction LGMIDIFromFile() {\r\n        this.properties = {\r\n            url: \"\",\r\n\t\t\tautoplay: true\r\n        };\r\n\r\n        this.addInput(\"play\", LiteGraph.ACTION);\r\n        this.addInput(\"pause\", LiteGraph.ACTION);\r\n        this.addOutput(\"note\", LiteGraph.EVENT);\r\n\t\tthis._midi = null;\r\n\t\tthis._current_time = 0;\r\n\t\tthis._playing = false;\r\n\r\n        if (typeof MidiParser == \"undefined\") {\r\n            console.error(\r\n                \"midi-parser.js not included, LGMidiPlay requires that library: https://raw.githubusercontent.com/colxi/midi-parser-js/master/src/main.js\"\r\n            );\r\n            this.boxcolor = \"red\";\r\n\t\t}\r\n\r\n\t}\r\n\r\n    LGMIDIFromFile.title = \"MIDI fromFile\";\r\n    LGMIDIFromFile.desc = \"Plays a MIDI file\";\r\n    LGMIDIFromFile.color = MIDI_COLOR;\r\n\r\n\tLGMIDIFromFile.prototype.onAction = function( name )\r\n\t{\r\n\t\tif(name == \"play\")\r\n\t\t\tthis.play();\r\n\t\telse if(name == \"pause\")\r\n\t\t\tthis._playing = !this._playing;\r\n\t}\r\n\r\n\tLGMIDIFromFile.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tif(name == \"url\")\r\n\t\t\tthis.loadMIDIFile(value);\r\n\t}\r\n\r\n    LGMIDIFromFile.prototype.onExecute = function() {\r\n\t\tif(!this._midi)\r\n\t\t\treturn;\r\n\r\n\t\tif(!this._playing)\r\n\t\t\treturn;\r\n\r\n\t\tthis._current_time += this.graph.elapsed_time;\r\n\t\tvar current_time = this._current_time * 100;\r\n\r\n\t\tfor(var i = 0; i < this._midi.tracks; ++i)\r\n\t\t{\r\n\t\t\tvar track = this._midi.track[i];\r\n\t\t\tif(!track._last_pos)\r\n\t\t\t{\r\n\t\t\t\ttrack._last_pos = 0;\r\n\t\t\t\ttrack._time = 0;\r\n\t\t\t}\r\n\r\n\t\t\tvar elem = track.event[ track._last_pos ];\r\n\t\t\tif(elem && (track._time + elem.deltaTime) <= current_time )\r\n\t\t\t{\r\n\t\t\t\ttrack._last_pos++;\r\n\t\t\t\ttrack._time += elem.deltaTime;\r\n\r\n\t\t\t\tif(elem.data)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar midi_cmd = elem.type << 4 + elem.channel;\r\n\t\t\t\t\tvar midi_event = new MIDIEvent();\r\n\t\t\t\t\tmidi_event.setup([midi_cmd, elem.data[0], elem.data[1]]);\r\n\t\t\t\t\tthis.trigger(\"note\", midi_event);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t}\r\n    };\r\n\r\n\tLGMIDIFromFile.prototype.play = function()\r\n\t{\r\n\t\tthis._playing = true;\r\n\t\tthis._current_time = 0;\r\n\t\tif(!this._midi)\r\n\t\t\treturn;\r\n\r\n\t\tfor(var i = 0; i < this._midi.tracks; ++i)\r\n\t\t{\r\n\t\t\tvar track = this._midi.track[i];\r\n\t\t\ttrack._last_pos = 0;\r\n\t\t\ttrack._time = 0;\r\n\t\t}\t\t\r\n\t}\r\n\r\n\tLGMIDIFromFile.prototype.loadMIDIFile = function(url)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tLiteGraph.fetchFile( url, \"arraybuffer\", function(data)\r\n\t\t{\r\n\t\t\tthat.boxcolor = \"#AFA\";\r\n\t\t\tthat._midi = MidiParser.parse( new Uint8Array(data) );\r\n\t\t\tif(that.properties.autoplay)\r\n\t\t\t\tthat.play();\r\n\t\t}, function(err){\r\n\t\t\tthat.boxcolor = \"#FAA\";\r\n\t\t\tthat._midi = null;\r\n\t\t});\r\n\t}\r\n\r\n\tLGMIDIFromFile.prototype.onDropFile = function(file)\r\n\t{\r\n\t\tthis.properties.url = \"\";\r\n\t\tthis.loadMIDIFile( file );\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"midi/fromFile\", LGMIDIFromFile);\r\n\r\n\r\n    function LGMIDIPlay() {\r\n        this.properties = {\r\n            volume: 0.5,\r\n            duration: 1\r\n        };\r\n        this.addInput(\"note\", LiteGraph.ACTION);\r\n        this.addInput(\"volume\", \"number\");\r\n        this.addInput(\"duration\", \"number\");\r\n        this.addOutput(\"note\", LiteGraph.EVENT);\r\n\r\n        if (typeof AudioSynth == \"undefined\") {\r\n            console.error(\r\n                \"Audiosynth.js not included, LGMidiPlay requires that library\"\r\n            );\r\n            this.boxcolor = \"red\";\r\n        } else {\r\n            var Synth = (this.synth = new AudioSynth());\r\n            this.instrument = Synth.createInstrument(\"piano\");\r\n        }\r\n    }\r\n\r\n    LGMIDIPlay.title = \"MIDI Play\";\r\n    LGMIDIPlay.desc = \"Plays a MIDI note\";\r\n    LGMIDIPlay.color = MIDI_COLOR;\r\n\r\n    LGMIDIPlay.prototype.onAction = function(event, midi_event) {\r\n        if (!midi_event || midi_event.constructor !== MIDIEvent) {\r\n            return;\r\n        }\r\n\r\n        if (this.instrument && midi_event.data[0] == MIDIEvent.NOTEON) {\r\n            var note = midi_event.note; //C#\r\n            if (!note || note == \"undefined\" || note.constructor !== String) {\r\n                return;\r\n            }\r\n            this.instrument.play(\r\n                note,\r\n                midi_event.octave,\r\n                this.properties.duration,\r\n                this.properties.volume\r\n            );\r\n        }\r\n        this.trigger(\"note\", midi_event);\r\n    };\r\n\r\n    LGMIDIPlay.prototype.onExecute = function() {\r\n        var volume = this.getInputData(1);\r\n        if (volume != null) {\r\n            this.properties.volume = volume;\r\n        }\r\n\r\n        var duration = this.getInputData(2);\r\n        if (duration != null) {\r\n            this.properties.duration = duration;\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/play\", LGMIDIPlay);\r\n\r\n    function LGMIDIKeys() {\r\n        this.properties = {\r\n            num_octaves: 2,\r\n            start_octave: 2\r\n        };\r\n        this.addInput(\"note\", LiteGraph.ACTION);\r\n        this.addInput(\"reset\", LiteGraph.ACTION);\r\n        this.addOutput(\"note\", LiteGraph.EVENT);\r\n        this.size = [400, 100];\r\n        this.keys = [];\r\n        this._last_key = -1;\r\n    }\r\n\r\n    LGMIDIKeys.title = \"MIDI Keys\";\r\n    LGMIDIKeys.desc = \"Keyboard to play notes\";\r\n    LGMIDIKeys.color = MIDI_COLOR;\r\n\r\n    LGMIDIKeys.keys = [\r\n        { x: 0, w: 1, h: 1, t: 0 },\r\n        { x: 0.75, w: 0.5, h: 0.6, t: 1 },\r\n        { x: 1, w: 1, h: 1, t: 0 },\r\n        { x: 1.75, w: 0.5, h: 0.6, t: 1 },\r\n        { x: 2, w: 1, h: 1, t: 0 },\r\n        { x: 2.75, w: 0.5, h: 0.6, t: 1 },\r\n        { x: 3, w: 1, h: 1, t: 0 },\r\n        { x: 4, w: 1, h: 1, t: 0 },\r\n        { x: 4.75, w: 0.5, h: 0.6, t: 1 },\r\n        { x: 5, w: 1, h: 1, t: 0 },\r\n        { x: 5.75, w: 0.5, h: 0.6, t: 1 },\r\n        { x: 6, w: 1, h: 1, t: 0 }\r\n    ];\r\n\r\n    LGMIDIKeys.prototype.onDrawForeground = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        var num_keys = this.properties.num_octaves * 12;\r\n        this.keys.length = num_keys;\r\n        var key_width = this.size[0] / (this.properties.num_octaves * 7);\r\n        var key_height = this.size[1];\r\n\r\n        ctx.globalAlpha = 1;\r\n\r\n        for (\r\n            var k = 0;\r\n            k < 2;\r\n            k++ //draw first whites (0) then blacks (1)\r\n        ) {\r\n            for (var i = 0; i < num_keys; ++i) {\r\n                var key_info = LGMIDIKeys.keys[i % 12];\r\n                if (key_info.t != k) {\r\n                    continue;\r\n                }\r\n                var octave = Math.floor(i / 12);\r\n                var x = octave * 7 * key_width + key_info.x * key_width;\r\n                if (k == 0) {\r\n                    ctx.fillStyle = this.keys[i] ? \"#CCC\" : \"white\";\r\n                } else {\r\n                    ctx.fillStyle = this.keys[i] ? \"#333\" : \"black\";\r\n                }\r\n                ctx.fillRect(\r\n                    x + 1,\r\n                    0,\r\n                    key_width * key_info.w - 2,\r\n                    key_height * key_info.h\r\n                );\r\n            }\r\n        }\r\n    };\r\n\r\n    LGMIDIKeys.prototype.getKeyIndex = function(pos) {\r\n        var num_keys = this.properties.num_octaves * 12;\r\n        var key_width = this.size[0] / (this.properties.num_octaves * 7);\r\n        var key_height = this.size[1];\r\n\r\n        for (\r\n            var k = 1;\r\n            k >= 0;\r\n            k-- //test blacks first (1) then whites (0)\r\n        ) {\r\n            for (var i = 0; i < this.keys.length; ++i) {\r\n                var key_info = LGMIDIKeys.keys[i % 12];\r\n                if (key_info.t != k) {\r\n                    continue;\r\n                }\r\n                var octave = Math.floor(i / 12);\r\n                var x = octave * 7 * key_width + key_info.x * key_width;\r\n                var w = key_width * key_info.w;\r\n                var h = key_height * key_info.h;\r\n                if (pos[0] < x || pos[0] > x + w || pos[1] > h) {\r\n                    continue;\r\n                }\r\n                return i;\r\n            }\r\n        }\r\n        return -1;\r\n    };\r\n\r\n    LGMIDIKeys.prototype.onAction = function(event, params) {\r\n        if (event == \"reset\") {\r\n            for (var i = 0; i < this.keys.length; ++i) {\r\n                this.keys[i] = false;\r\n            }\r\n            return;\r\n        }\r\n\r\n        if (!params || params.constructor !== MIDIEvent) {\r\n            return;\r\n        }\r\n        var midi_event = params;\r\n        var start_note = (this.properties.start_octave - 1) * 12 + 29;\r\n        var index = midi_event.data[1] - start_note;\r\n        if (index >= 0 && index < this.keys.length) {\r\n            if (midi_event.data[0] == MIDIEvent.NOTEON) {\r\n                this.keys[index] = true;\r\n            } else if (midi_event.data[0] == MIDIEvent.NOTEOFF) {\r\n                this.keys[index] = false;\r\n            }\r\n        }\r\n\r\n        this.trigger(\"note\", midi_event);\r\n    };\r\n\r\n    LGMIDIKeys.prototype.onMouseDown = function(e, pos) {\r\n        if (pos[1] < 0) {\r\n            return;\r\n        }\r\n        var index = this.getKeyIndex(pos);\r\n        this.keys[index] = true;\r\n        this._last_key = index;\r\n        var pitch = (this.properties.start_octave - 1) * 12 + 29 + index;\r\n        var midi_event = new MIDIEvent();\r\n        midi_event.setup([MIDIEvent.NOTEON, pitch, 100]);\r\n        this.trigger(\"note\", midi_event);\r\n        return true;\r\n    };\r\n\r\n    LGMIDIKeys.prototype.onMouseMove = function(e, pos) {\r\n        if (pos[1] < 0 || this._last_key == -1) {\r\n            return;\r\n        }\r\n        this.setDirtyCanvas(true);\r\n        var index = this.getKeyIndex(pos);\r\n        if (this._last_key == index) {\r\n            return true;\r\n        }\r\n        this.keys[this._last_key] = false;\r\n        var pitch =\r\n            (this.properties.start_octave - 1) * 12 + 29 + this._last_key;\r\n        var midi_event = new MIDIEvent();\r\n        midi_event.setup([MIDIEvent.NOTEOFF, pitch, 100]);\r\n        this.trigger(\"note\", midi_event);\r\n\r\n        this.keys[index] = true;\r\n        var pitch = (this.properties.start_octave - 1) * 12 + 29 + index;\r\n        var midi_event = new MIDIEvent();\r\n        midi_event.setup([MIDIEvent.NOTEON, pitch, 100]);\r\n        this.trigger(\"note\", midi_event);\r\n\r\n        this._last_key = index;\r\n        return true;\r\n    };\r\n\r\n    LGMIDIKeys.prototype.onMouseUp = function(e, pos) {\r\n        if (pos[1] < 0) {\r\n            return;\r\n        }\r\n        var index = this.getKeyIndex(pos);\r\n        this.keys[index] = false;\r\n        this._last_key = -1;\r\n        var pitch = (this.properties.start_octave - 1) * 12 + 29 + index;\r\n        var midi_event = new MIDIEvent();\r\n        midi_event.setup([MIDIEvent.NOTEOFF, pitch, 100]);\r\n        this.trigger(\"note\", midi_event);\r\n        return true;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/keys\", LGMIDIKeys);\r\n\r\n    function now() {\r\n        return window.performance.now();\r\n    }\r\n})(this);\r\n\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    var LGAudio = {};\r\n    global.LGAudio = LGAudio;\r\n\r\n    LGAudio.getAudioContext = function() {\r\n        if (!this._audio_context) {\r\n            window.AudioContext =\r\n                window.AudioContext || window.webkitAudioContext;\r\n            if (!window.AudioContext) {\r\n                console.error(\"AudioContext not supported by browser\");\r\n                return null;\r\n            }\r\n            this._audio_context = new AudioContext();\r\n            this._audio_context.onmessage = function(msg) {\r\n                console.log(\"msg\", msg);\r\n            };\r\n            this._audio_context.onended = function(msg) {\r\n                console.log(\"ended\", msg);\r\n            };\r\n            this._audio_context.oncomplete = function(msg) {\r\n                console.log(\"complete\", msg);\r\n            };\r\n        }\r\n\r\n        //in case it crashes\r\n        //if(this._audio_context.state == \"suspended\")\r\n        //\tthis._audio_context.resume();\r\n        return this._audio_context;\r\n    };\r\n\r\n    LGAudio.connect = function(audionodeA, audionodeB) {\r\n        try {\r\n            audionodeA.connect(audionodeB);\r\n        } catch (err) {\r\n            console.warn(\"LGraphAudio:\", err);\r\n        }\r\n    };\r\n\r\n    LGAudio.disconnect = function(audionodeA, audionodeB) {\r\n        try {\r\n            audionodeA.disconnect(audionodeB);\r\n        } catch (err) {\r\n            console.warn(\"LGraphAudio:\", err);\r\n        }\r\n    };\r\n\r\n    LGAudio.changeAllAudiosConnections = function(node, connect) {\r\n        if (node.inputs) {\r\n            for (var i = 0; i < node.inputs.length; ++i) {\r\n                var input = node.inputs[i];\r\n                var link_info = node.graph.links[input.link];\r\n                if (!link_info) {\r\n                    continue;\r\n                }\r\n\r\n                var origin_node = node.graph.getNodeById(link_info.origin_id);\r\n                var origin_audionode = null;\r\n                if (origin_node.getAudioNodeInOutputSlot) {\r\n                    origin_audionode = origin_node.getAudioNodeInOutputSlot(\r\n                        link_info.origin_slot\r\n                    );\r\n                } else {\r\n                    origin_audionode = origin_node.audionode;\r\n                }\r\n\r\n                var target_audionode = null;\r\n                if (node.getAudioNodeInInputSlot) {\r\n                    target_audionode = node.getAudioNodeInInputSlot(i);\r\n                } else {\r\n                    target_audionode = node.audionode;\r\n                }\r\n\r\n                if (connect) {\r\n                    LGAudio.connect(origin_audionode, target_audionode);\r\n                } else {\r\n                    LGAudio.disconnect(origin_audionode, target_audionode);\r\n                }\r\n            }\r\n        }\r\n\r\n        if (node.outputs) {\r\n            for (var i = 0; i < node.outputs.length; ++i) {\r\n                var output = node.outputs[i];\r\n                for (var j = 0; j < output.links.length; ++j) {\r\n                    var link_info = node.graph.links[output.links[j]];\r\n                    if (!link_info) {\r\n                        continue;\r\n                    }\r\n\r\n                    var origin_audionode = null;\r\n                    if (node.getAudioNodeInOutputSlot) {\r\n                        origin_audionode = node.getAudioNodeInOutputSlot(i);\r\n                    } else {\r\n                        origin_audionode = node.audionode;\r\n                    }\r\n\r\n                    var target_node = node.graph.getNodeById(\r\n                        link_info.target_id\r\n                    );\r\n                    var target_audionode = null;\r\n                    if (target_node.getAudioNodeInInputSlot) {\r\n                        target_audionode = target_node.getAudioNodeInInputSlot(\r\n                            link_info.target_slot\r\n                        );\r\n                    } else {\r\n                        target_audionode = target_node.audionode;\r\n                    }\r\n\r\n                    if (connect) {\r\n                        LGAudio.connect(origin_audionode, target_audionode);\r\n                    } else {\r\n                        LGAudio.disconnect(origin_audionode, target_audionode);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    //used by many nodes\r\n    LGAudio.onConnectionsChange = function(\r\n        connection,\r\n        slot,\r\n        connected,\r\n        link_info\r\n    ) {\r\n        //only process the outputs events\r\n        if (connection != LiteGraph.OUTPUT) {\r\n            return;\r\n        }\r\n\r\n        var target_node = null;\r\n        if (link_info) {\r\n            target_node = this.graph.getNodeById(link_info.target_id);\r\n        }\r\n\r\n        if (!target_node) {\r\n            return;\r\n        }\r\n\r\n        //get origin audionode\r\n        var local_audionode = null;\r\n        if (this.getAudioNodeInOutputSlot) {\r\n            local_audionode = this.getAudioNodeInOutputSlot(slot);\r\n        } else {\r\n            local_audionode = this.audionode;\r\n        }\r\n\r\n        //get target audionode\r\n        var target_audionode = null;\r\n        if (target_node.getAudioNodeInInputSlot) {\r\n            target_audionode = target_node.getAudioNodeInInputSlot(\r\n                link_info.target_slot\r\n            );\r\n        } else {\r\n            target_audionode = target_node.audionode;\r\n        }\r\n\r\n        //do the connection/disconnection\r\n        if (connected) {\r\n            LGAudio.connect(local_audionode, target_audionode);\r\n        } else {\r\n            LGAudio.disconnect(local_audionode, target_audionode);\r\n        }\r\n    };\r\n\r\n    //this function helps creating wrappers to existing classes\r\n    LGAudio.createAudioNodeWrapper = function(class_object) {\r\n        var old_func = class_object.prototype.onPropertyChanged;\r\n\r\n        class_object.prototype.onPropertyChanged = function(name, value) {\r\n            if (old_func) {\r\n                old_func.call(this, name, value);\r\n            }\r\n\r\n            if (!this.audionode) {\r\n                return;\r\n            }\r\n\r\n            if (this.audionode[name] === undefined) {\r\n                return;\r\n            }\r\n\r\n            if (this.audionode[name].value !== undefined) {\r\n                this.audionode[name].value = value;\r\n            } else {\r\n                this.audionode[name] = value;\r\n            }\r\n        };\r\n\r\n        class_object.prototype.onConnectionsChange =\r\n            LGAudio.onConnectionsChange;\r\n    };\r\n\r\n    //contains the samples decoded of the loaded audios in AudioBuffer format\r\n    LGAudio.cached_audios = {};\r\n\r\n    LGAudio.loadSound = function(url, on_complete, on_error) {\r\n        if (LGAudio.cached_audios[url] && url.indexOf(\"blob:\") == -1) {\r\n            if (on_complete) {\r\n                on_complete(LGAudio.cached_audios[url]);\r\n            }\r\n            return;\r\n        }\r\n\r\n        if (LGAudio.onProcessAudioURL) {\r\n            url = LGAudio.onProcessAudioURL(url);\r\n        }\r\n\r\n        //load new sample\r\n        var request = new XMLHttpRequest();\r\n        request.open(\"GET\", url, true);\r\n        request.responseType = \"arraybuffer\";\r\n\r\n        var context = LGAudio.getAudioContext();\r\n\r\n        // Decode asynchronously\r\n        request.onload = function() {\r\n            console.log(\"AudioSource loaded\");\r\n            context.decodeAudioData(\r\n                request.response,\r\n                function(buffer) {\r\n                    console.log(\"AudioSource decoded\");\r\n                    LGAudio.cached_audios[url] = buffer;\r\n                    if (on_complete) {\r\n                        on_complete(buffer);\r\n                    }\r\n                },\r\n                onError\r\n            );\r\n        };\r\n        request.send();\r\n\r\n        function onError(err) {\r\n            console.log(\"Audio loading sample error:\", err);\r\n            if (on_error) {\r\n                on_error(err);\r\n            }\r\n        }\r\n\r\n        return request;\r\n    };\r\n\r\n    //****************************************************\r\n\r\n    function LGAudioSource() {\r\n        this.properties = {\r\n            src: \"\",\r\n            gain: 0.5,\r\n            loop: true,\r\n            autoplay: true,\r\n            playbackRate: 1\r\n        };\r\n\r\n        this._loading_audio = false;\r\n        this._audiobuffer = null; //points to AudioBuffer with the audio samples decoded\r\n        this._audionodes = [];\r\n        this._last_sourcenode = null; //the last AudioBufferSourceNode (there could be more if there are several sounds playing)\r\n\r\n        this.addOutput(\"out\", \"audio\");\r\n        this.addInput(\"gain\", \"number\");\r\n\r\n        //init context\r\n        var context = LGAudio.getAudioContext();\r\n\r\n        //create gain node to control volume\r\n        this.audionode = context.createGain();\r\n        this.audionode.graphnode = this;\r\n        this.audionode.gain.value = this.properties.gain;\r\n\r\n        //debug\r\n        if (this.properties.src) {\r\n            this.loadSound(this.properties.src);\r\n        }\r\n    }\r\n\r\n\tLGAudioSource.desc = \"Plays an audio file\";\r\n    LGAudioSource[\"@src\"] = { widget: \"resource\" };\r\n    LGAudioSource.supported_extensions = [\"wav\", \"ogg\", \"mp3\"];\r\n\r\n    LGAudioSource.prototype.onAdded = function(graph) {\r\n        if (graph.status === LGraph.STATUS_RUNNING) {\r\n            this.onStart();\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.onStart = function() {\r\n        if (!this._audiobuffer) {\r\n            return;\r\n        }\r\n\r\n        if (this.properties.autoplay) {\r\n\t\t\tthis.playBuffer(this._audiobuffer);\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.onStop = function() {\r\n        this.stopAllSounds();\r\n    };\r\n\r\n    LGAudioSource.prototype.onPause = function() {\r\n        this.pauseAllSounds();\r\n    };\r\n\r\n    LGAudioSource.prototype.onUnpause = function() {\r\n        this.unpauseAllSounds();\r\n        //this.onStart();\r\n    };\r\n\r\n    LGAudioSource.prototype.onRemoved = function() {\r\n        this.stopAllSounds();\r\n        if (this._dropped_url) {\r\n            URL.revokeObjectURL(this._url);\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.stopAllSounds = function() {\r\n        //iterate and stop\r\n        for (var i = 0; i < this._audionodes.length; ++i) {\r\n            if (this._audionodes[i].started) {\r\n                this._audionodes[i].started = false;\r\n                this._audionodes[i].stop();\r\n            }\r\n            //this._audionodes[i].disconnect( this.audionode );\r\n        }\r\n        this._audionodes.length = 0;\r\n    };\r\n\r\n    LGAudioSource.prototype.pauseAllSounds = function() {\r\n        LGAudio.getAudioContext().suspend();\r\n    };\r\n\r\n    LGAudioSource.prototype.unpauseAllSounds = function() {\r\n        LGAudio.getAudioContext().resume();\r\n    };\r\n\r\n    LGAudioSource.prototype.onExecute = function() {\r\n        if (this.inputs) {\r\n            for (var i = 0; i < this.inputs.length; ++i) {\r\n                var input = this.inputs[i];\r\n                if (input.link == null) {\r\n                    continue;\r\n                }\r\n                var v = this.getInputData(i);\r\n                if (v === undefined) {\r\n                    continue;\r\n                }\r\n                if (input.name == \"gain\")\r\n                    this.audionode.gain.value = v;\r\n                else if (input.name == \"src\") {\r\n                    this.setProperty(\"src\",v);\r\n                } else if (input.name == \"playbackRate\") {\r\n                    this.properties.playbackRate = v;\r\n                    for (var j = 0; j < this._audionodes.length; ++j) {\r\n                        this._audionodes[j].playbackRate.value = v;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        if (this.outputs) {\r\n            for (var i = 0; i < this.outputs.length; ++i) {\r\n                var output = this.outputs[i];\r\n                if (output.name == \"buffer\" && this._audiobuffer) {\r\n                    this.setOutputData(i, this._audiobuffer);\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.onAction = function(event) {\r\n        if (this._audiobuffer) {\r\n            if (event == \"Play\") {\r\n                this.playBuffer(this._audiobuffer);\r\n            } else if (event == \"Stop\") {\r\n                this.stopAllSounds();\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"src\") {\r\n            this.loadSound(value);\r\n        } else if (name == \"gain\") {\r\n            this.audionode.gain.value = value;\r\n        } else if (name == \"playbackRate\") {\r\n            for (var j = 0; j < this._audionodes.length; ++j) {\r\n                this._audionodes[j].playbackRate.value = value;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.playBuffer = function(buffer) {\r\n        var that = this;\r\n        var context = LGAudio.getAudioContext();\r\n\r\n        //create a new audionode (this is mandatory, AudioAPI doesnt like to reuse old ones)\r\n        var audionode = context.createBufferSource(); //create a AudioBufferSourceNode\r\n        this._last_sourcenode = audionode;\r\n        audionode.graphnode = this;\r\n        audionode.buffer = buffer;\r\n        audionode.loop = this.properties.loop;\r\n        audionode.playbackRate.value = this.properties.playbackRate;\r\n        this._audionodes.push(audionode);\r\n        audionode.connect(this.audionode); //connect to gain\r\n\r\n\t\tthis._audionodes.push(audionode);\r\n\r\n\t\tthis.trigger(\"start\");\r\n\r\n        audionode.onended = function() {\r\n            //console.log(\"ended!\");\r\n            that.trigger(\"ended\");\r\n            //remove\r\n            var index = that._audionodes.indexOf(audionode);\r\n            if (index != -1) {\r\n                that._audionodes.splice(index, 1);\r\n            }\r\n        };\r\n\r\n        if (!audionode.started) {\r\n            audionode.started = true;\r\n            audionode.start();\r\n        }\r\n        return audionode;\r\n    };\r\n\r\n    LGAudioSource.prototype.loadSound = function(url) {\r\n        var that = this;\r\n\r\n        //kill previous load\r\n        if (this._request) {\r\n            this._request.abort();\r\n            this._request = null;\r\n        }\r\n\r\n        this._audiobuffer = null; //points to the audiobuffer once the audio is loaded\r\n        this._loading_audio = false;\r\n\r\n        if (!url) {\r\n            return;\r\n        }\r\n\r\n        this._request = LGAudio.loadSound(url, inner);\r\n\r\n        this._loading_audio = true;\r\n        this.boxcolor = \"#AA4\";\r\n\r\n        function inner(buffer) {\r\n            this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR;\r\n            that._audiobuffer = buffer;\r\n            that._loading_audio = false;\r\n            //if is playing, then play it\r\n            if (that.graph && that.graph.status === LGraph.STATUS_RUNNING) {\r\n                that.onStart();\r\n            } //this controls the autoplay already\r\n        }\r\n    };\r\n\r\n    //Helps connect/disconnect AudioNodes when new connections are made in the node\r\n    LGAudioSource.prototype.onConnectionsChange = LGAudio.onConnectionsChange;\r\n\r\n    LGAudioSource.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"playbackRate\", \"number\"],\r\n\t\t\t[\"src\",\"string\"],\r\n            [\"Play\", LiteGraph.ACTION],\r\n            [\"Stop\", LiteGraph.ACTION]\r\n        ];\r\n    };\r\n\r\n    LGAudioSource.prototype.onGetOutputs = function() {\r\n        return [[\"buffer\", \"audiobuffer\"], [\"start\", LiteGraph.EVENT], [\"ended\", LiteGraph.EVENT]];\r\n    };\r\n\r\n    LGAudioSource.prototype.onDropFile = function(file) {\r\n        if (this._dropped_url) {\r\n            URL.revokeObjectURL(this._dropped_url);\r\n        }\r\n        var url = URL.createObjectURL(file);\r\n        this.properties.src = url;\r\n        this.loadSound(url);\r\n        this._dropped_url = url;\r\n    };\r\n\r\n    LGAudioSource.title = \"Source\";\r\n    LGAudioSource.desc = \"Plays audio\";\r\n    LiteGraph.registerNodeType(\"audio/source\", LGAudioSource);\r\n\r\n    //****************************************************\r\n\r\n    function LGAudioMediaSource() {\r\n        this.properties = {\r\n            gain: 0.5\r\n        };\r\n\r\n        this._audionodes = [];\r\n        this._media_stream = null;\r\n\r\n        this.addOutput(\"out\", \"audio\");\r\n        this.addInput(\"gain\", \"number\");\r\n\r\n        //create gain node to control volume\r\n        var context = LGAudio.getAudioContext();\r\n        this.audionode = context.createGain();\r\n        this.audionode.graphnode = this;\r\n        this.audionode.gain.value = this.properties.gain;\r\n    }\r\n\r\n    LGAudioMediaSource.prototype.onAdded = function(graph) {\r\n        if (graph.status === LGraph.STATUS_RUNNING) {\r\n            this.onStart();\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onStart = function() {\r\n        if (this._media_stream == null && !this._waiting_confirmation) {\r\n            this.openStream();\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onStop = function() {\r\n        this.audionode.gain.value = 0;\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onPause = function() {\r\n        this.audionode.gain.value = 0;\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onUnpause = function() {\r\n        this.audionode.gain.value = this.properties.gain;\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onRemoved = function() {\r\n        this.audionode.gain.value = 0;\r\n        if (this.audiosource_node) {\r\n            this.audiosource_node.disconnect(this.audionode);\r\n            this.audiosource_node = null;\r\n        }\r\n        if (this._media_stream) {\r\n            var tracks = this._media_stream.getTracks();\r\n            if (tracks.length) {\r\n                tracks[0].stop();\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.openStream = function() {\r\n        if (!navigator.mediaDevices) {\r\n            console.log(\r\n                \"getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags\"\r\n            );\r\n            return;\r\n        }\r\n\r\n        this._waiting_confirmation = true;\r\n\r\n        // Not showing vendor prefixes.\r\n        navigator.mediaDevices\r\n            .getUserMedia({ audio: true, video: false })\r\n            .then(this.streamReady.bind(this))\r\n            .catch(onFailSoHard);\r\n\r\n        var that = this;\r\n        function onFailSoHard(err) {\r\n            console.log(\"Media rejected\", err);\r\n            that._media_stream = false;\r\n            that.boxcolor = \"red\";\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.streamReady = function(localMediaStream) {\r\n        this._media_stream = localMediaStream;\r\n        //this._waiting_confirmation = false;\r\n\r\n        //init context\r\n        if (this.audiosource_node) {\r\n            this.audiosource_node.disconnect(this.audionode);\r\n        }\r\n        var context = LGAudio.getAudioContext();\r\n        this.audiosource_node = context.createMediaStreamSource(\r\n            localMediaStream\r\n        );\r\n        this.audiosource_node.graphnode = this;\r\n        this.audiosource_node.connect(this.audionode);\r\n        this.boxcolor = \"white\";\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onExecute = function() {\r\n        if (this._media_stream == null && !this._waiting_confirmation) {\r\n            this.openStream();\r\n        }\r\n\r\n        if (this.inputs) {\r\n            for (var i = 0; i < this.inputs.length; ++i) {\r\n                var input = this.inputs[i];\r\n                if (input.link == null) {\r\n                    continue;\r\n                }\r\n                var v = this.getInputData(i);\r\n                if (v === undefined) {\r\n                    continue;\r\n                }\r\n                if (input.name == \"gain\") {\r\n                    this.audionode.gain.value = this.properties.gain = v;\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onAction = function(event) {\r\n        if (event == \"Play\") {\r\n            this.audionode.gain.value = this.properties.gain;\r\n        } else if (event == \"Stop\") {\r\n            this.audionode.gain.value = 0;\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"gain\") {\r\n            this.audionode.gain.value = value;\r\n        }\r\n    };\r\n\r\n    //Helps connect/disconnect AudioNodes when new connections are made in the node\r\n    LGAudioMediaSource.prototype.onConnectionsChange =\r\n        LGAudio.onConnectionsChange;\r\n\r\n    LGAudioMediaSource.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"playbackRate\", \"number\"],\r\n            [\"Play\", LiteGraph.ACTION],\r\n            [\"Stop\", LiteGraph.ACTION]\r\n        ];\r\n    };\r\n\r\n    LGAudioMediaSource.title = \"MediaSource\";\r\n    LGAudioMediaSource.desc = \"Plays microphone\";\r\n    LiteGraph.registerNodeType(\"audio/media_source\", LGAudioMediaSource);\r\n\r\n    //*****************************************************\r\n\r\n    function LGAudioAnalyser() {\r\n        this.properties = {\r\n            fftSize: 2048,\r\n            minDecibels: -100,\r\n            maxDecibels: -10,\r\n            smoothingTimeConstant: 0.5\r\n        };\r\n\r\n        var context = LGAudio.getAudioContext();\r\n\r\n        this.audionode = context.createAnalyser();\r\n        this.audionode.graphnode = this;\r\n        this.audionode.fftSize = this.properties.fftSize;\r\n        this.audionode.minDecibels = this.properties.minDecibels;\r\n        this.audionode.maxDecibels = this.properties.maxDecibels;\r\n        this.audionode.smoothingTimeConstant = this.properties.smoothingTimeConstant;\r\n\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addOutput(\"freqs\", \"array\");\r\n        this.addOutput(\"samples\", \"array\");\r\n\r\n        this._freq_bin = null;\r\n        this._time_bin = null;\r\n    }\r\n\r\n    LGAudioAnalyser.prototype.onPropertyChanged = function(name, value) {\r\n        this.audionode[name] = value;\r\n    };\r\n\r\n    LGAudioAnalyser.prototype.onExecute = function() {\r\n        if (this.isOutputConnected(0)) {\r\n            //send FFT\r\n            var bufferLength = this.audionode.frequencyBinCount;\r\n            if (!this._freq_bin || this._freq_bin.length != bufferLength) {\r\n                this._freq_bin = new Uint8Array(bufferLength);\r\n            }\r\n            this.audionode.getByteFrequencyData(this._freq_bin);\r\n            this.setOutputData(0, this._freq_bin);\r\n        }\r\n\r\n        //send analyzer\r\n        if (this.isOutputConnected(1)) {\r\n            //send Samples\r\n            var bufferLength = this.audionode.frequencyBinCount;\r\n            if (!this._time_bin || this._time_bin.length != bufferLength) {\r\n                this._time_bin = new Uint8Array(bufferLength);\r\n            }\r\n            this.audionode.getByteTimeDomainData(this._time_bin);\r\n            this.setOutputData(1, this._time_bin);\r\n        }\r\n\r\n        //properties\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n            if (input.link == null) {\r\n                continue;\r\n            }\r\n            var v = this.getInputData(i);\r\n            if (v !== undefined) {\r\n                this.audionode[input.name].value = v;\r\n            }\r\n        }\r\n\r\n        //time domain\r\n        //this.audionode.getFloatTimeDomainData( dataArray );\r\n    };\r\n\r\n    LGAudioAnalyser.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"minDecibels\", \"number\"],\r\n            [\"maxDecibels\", \"number\"],\r\n            [\"smoothingTimeConstant\", \"number\"]\r\n        ];\r\n    };\r\n\r\n    LGAudioAnalyser.prototype.onGetOutputs = function() {\r\n        return [[\"freqs\", \"array\"], [\"samples\", \"array\"]];\r\n    };\r\n\r\n    LGAudioAnalyser.title = \"Analyser\";\r\n    LGAudioAnalyser.desc = \"Audio Analyser\";\r\n    LiteGraph.registerNodeType(\"audio/analyser\", LGAudioAnalyser);\r\n\r\n    //*****************************************************\r\n\r\n    function LGAudioGain() {\r\n        //default\r\n        this.properties = {\r\n            gain: 1\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createGain();\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addInput(\"gain\", \"number\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioGain.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n            var v = this.getInputData(i);\r\n            if (v !== undefined) {\r\n                this.audionode[input.name].value = v;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioGain);\r\n\r\n    LGAudioGain.title = \"Gain\";\r\n    LGAudioGain.desc = \"Audio gain\";\r\n    LiteGraph.registerNodeType(\"audio/gain\", LGAudioGain);\r\n\r\n    function LGAudioConvolver() {\r\n        //default\r\n        this.properties = {\r\n            impulse_src: \"\",\r\n            normalize: true\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createConvolver();\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioConvolver);\r\n\r\n    LGAudioConvolver.prototype.onRemove = function() {\r\n        if (this._dropped_url) {\r\n            URL.revokeObjectURL(this._dropped_url);\r\n        }\r\n    };\r\n\r\n    LGAudioConvolver.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"impulse_src\") {\r\n            this.loadImpulse(value);\r\n        } else if (name == \"normalize\") {\r\n            this.audionode.normalize = value;\r\n        }\r\n    };\r\n\r\n    LGAudioConvolver.prototype.onDropFile = function(file) {\r\n        if (this._dropped_url) {\r\n            URL.revokeObjectURL(this._dropped_url);\r\n        }\r\n        this._dropped_url = URL.createObjectURL(file);\r\n        this.properties.impulse_src = this._dropped_url;\r\n        this.loadImpulse(this._dropped_url);\r\n    };\r\n\r\n    LGAudioConvolver.prototype.loadImpulse = function(url) {\r\n        var that = this;\r\n\r\n        //kill previous load\r\n        if (this._request) {\r\n            this._request.abort();\r\n            this._request = null;\r\n        }\r\n\r\n        this._impulse_buffer = null;\r\n        this._loading_impulse = false;\r\n\r\n        if (!url) {\r\n            return;\r\n        }\r\n\r\n        //load new sample\r\n        this._request = LGAudio.loadSound(url, inner);\r\n        this._loading_impulse = true;\r\n\r\n        // Decode asynchronously\r\n        function inner(buffer) {\r\n            that._impulse_buffer = buffer;\r\n            that.audionode.buffer = buffer;\r\n            console.log(\"Impulse signal set\");\r\n            that._loading_impulse = false;\r\n        }\r\n    };\r\n\r\n    LGAudioConvolver.title = \"Convolver\";\r\n    LGAudioConvolver.desc = \"Convolves the signal (used for reverb)\";\r\n    LiteGraph.registerNodeType(\"audio/convolver\", LGAudioConvolver);\r\n\r\n    function LGAudioDynamicsCompressor() {\r\n        //default\r\n        this.properties = {\r\n            threshold: -50,\r\n            knee: 40,\r\n            ratio: 12,\r\n            reduction: -20,\r\n            attack: 0,\r\n            release: 0.25\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createDynamicsCompressor();\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioDynamicsCompressor);\r\n\r\n    LGAudioDynamicsCompressor.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n            if (input.link == null) {\r\n                continue;\r\n            }\r\n            var v = this.getInputData(i);\r\n            if (v !== undefined) {\r\n                this.audionode[input.name].value = v;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioDynamicsCompressor.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"threshold\", \"number\"],\r\n            [\"knee\", \"number\"],\r\n            [\"ratio\", \"number\"],\r\n            [\"reduction\", \"number\"],\r\n            [\"attack\", \"number\"],\r\n            [\"release\", \"number\"]\r\n        ];\r\n    };\r\n\r\n    LGAudioDynamicsCompressor.title = \"DynamicsCompressor\";\r\n    LGAudioDynamicsCompressor.desc = \"Dynamics Compressor\";\r\n    LiteGraph.registerNodeType(\r\n        \"audio/dynamicsCompressor\",\r\n        LGAudioDynamicsCompressor\r\n    );\r\n\r\n    function LGAudioWaveShaper() {\r\n        //default\r\n        this.properties = {};\r\n\r\n        this.audionode = LGAudio.getAudioContext().createWaveShaper();\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addInput(\"shape\", \"waveshape\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioWaveShaper.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n        var v = this.getInputData(1);\r\n        if (v === undefined) {\r\n            return;\r\n        }\r\n        this.audionode.curve = v;\r\n    };\r\n\r\n    LGAudioWaveShaper.prototype.setWaveShape = function(shape) {\r\n        this.audionode.curve = shape;\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioWaveShaper);\r\n\r\n    /* disabled till I dont find a way to do a wave shape\r\nLGAudioWaveShaper.title = \"WaveShaper\";\r\nLGAudioWaveShaper.desc = \"Distortion using wave shape\";\r\nLiteGraph.registerNodeType(\"audio/waveShaper\", LGAudioWaveShaper);\r\n*/\r\n\r\n    function LGAudioMixer() {\r\n        //default\r\n        this.properties = {\r\n            gain1: 0.5,\r\n            gain2: 0.5\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createGain();\r\n\r\n        this.audionode1 = LGAudio.getAudioContext().createGain();\r\n        this.audionode1.gain.value = this.properties.gain1;\r\n        this.audionode2 = LGAudio.getAudioContext().createGain();\r\n        this.audionode2.gain.value = this.properties.gain2;\r\n\r\n        this.audionode1.connect(this.audionode);\r\n        this.audionode2.connect(this.audionode);\r\n\r\n        this.addInput(\"in1\", \"audio\");\r\n        this.addInput(\"in1 gain\", \"number\");\r\n        this.addInput(\"in2\", \"audio\");\r\n        this.addInput(\"in2 gain\", \"number\");\r\n\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioMixer.prototype.getAudioNodeInInputSlot = function(slot) {\r\n        if (slot == 0) {\r\n            return this.audionode1;\r\n        } else if (slot == 2) {\r\n            return this.audionode2;\r\n        }\r\n    };\r\n\r\n    LGAudioMixer.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"gain1\") {\r\n            this.audionode1.gain.value = value;\r\n        } else if (name == \"gain2\") {\r\n            this.audionode2.gain.value = value;\r\n        }\r\n    };\r\n\r\n    LGAudioMixer.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n\r\n            if (input.link == null || input.type == \"audio\") {\r\n                continue;\r\n            }\r\n\r\n            var v = this.getInputData(i);\r\n            if (v === undefined) {\r\n                continue;\r\n            }\r\n\r\n            if (i == 1) {\r\n                this.audionode1.gain.value = v;\r\n            } else if (i == 3) {\r\n                this.audionode2.gain.value = v;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioMixer);\r\n\r\n    LGAudioMixer.title = \"Mixer\";\r\n    LGAudioMixer.desc = \"Audio mixer\";\r\n    LiteGraph.registerNodeType(\"audio/mixer\", LGAudioMixer);\r\n\r\n    function LGAudioADSR() {\r\n        //default\r\n        this.properties = {\r\n            A: 0.1,\r\n            D: 0.1,\r\n            S: 0.1,\r\n            R: 0.1\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createGain();\r\n        this.audionode.gain.value = 0;\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addInput(\"gate\", \"boolean\");\r\n        this.addOutput(\"out\", \"audio\");\r\n        this.gate = false;\r\n    }\r\n\r\n    LGAudioADSR.prototype.onExecute = function() {\r\n        var audioContext = LGAudio.getAudioContext();\r\n        var now = audioContext.currentTime;\r\n        var node = this.audionode;\r\n        var gain = node.gain;\r\n        var current_gate = this.getInputData(1);\r\n\r\n        var A = this.getInputOrProperty(\"A\");\r\n        var D = this.getInputOrProperty(\"D\");\r\n        var S = this.getInputOrProperty(\"S\");\r\n        var R = this.getInputOrProperty(\"R\");\r\n\r\n        if (!this.gate && current_gate) {\r\n            gain.cancelScheduledValues(0);\r\n            gain.setValueAtTime(0, now);\r\n            gain.linearRampToValueAtTime(1, now + A);\r\n            gain.linearRampToValueAtTime(S, now + A + D);\r\n        } else if (this.gate && !current_gate) {\r\n            gain.cancelScheduledValues(0);\r\n            gain.setValueAtTime(gain.value, now);\r\n            gain.linearRampToValueAtTime(0, now + R);\r\n        }\r\n\r\n        this.gate = current_gate;\r\n    };\r\n\r\n    LGAudioADSR.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"A\", \"number\"],\r\n            [\"D\", \"number\"],\r\n            [\"S\", \"number\"],\r\n            [\"R\", \"number\"]\r\n        ];\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioADSR);\r\n\r\n    LGAudioADSR.title = \"ADSR\";\r\n    LGAudioADSR.desc = \"Audio envelope\";\r\n    LiteGraph.registerNodeType(\"audio/adsr\", LGAudioADSR);\r\n\r\n    function LGAudioDelay() {\r\n        //default\r\n        this.properties = {\r\n            delayTime: 0.5\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createDelay(10);\r\n        this.audionode.delayTime.value = this.properties.delayTime;\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addInput(\"time\", \"number\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioDelay);\r\n\r\n    LGAudioDelay.prototype.onExecute = function() {\r\n        var v = this.getInputData(1);\r\n        if (v !== undefined) {\r\n            this.audionode.delayTime.value = v;\r\n        }\r\n    };\r\n\r\n    LGAudioDelay.title = \"Delay\";\r\n    LGAudioDelay.desc = \"Audio delay\";\r\n    LiteGraph.registerNodeType(\"audio/delay\", LGAudioDelay);\r\n\r\n    function LGAudioBiquadFilter() {\r\n        //default\r\n        this.properties = {\r\n            frequency: 350,\r\n            detune: 0,\r\n            Q: 1\r\n        };\r\n        this.addProperty(\"type\", \"lowpass\", \"enum\", {\r\n            values: [\r\n                \"lowpass\",\r\n                \"highpass\",\r\n                \"bandpass\",\r\n                \"lowshelf\",\r\n                \"highshelf\",\r\n                \"peaking\",\r\n                \"notch\",\r\n                \"allpass\"\r\n            ]\r\n        });\r\n\r\n        //create node\r\n        this.audionode = LGAudio.getAudioContext().createBiquadFilter();\r\n\r\n        //slots\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioBiquadFilter.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n            if (input.link == null) {\r\n                continue;\r\n            }\r\n            var v = this.getInputData(i);\r\n            if (v !== undefined) {\r\n                this.audionode[input.name].value = v;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioBiquadFilter.prototype.onGetInputs = function() {\r\n        return [[\"frequency\", \"number\"], [\"detune\", \"number\"], [\"Q\", \"number\"]];\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioBiquadFilter);\r\n\r\n    LGAudioBiquadFilter.title = \"BiquadFilter\";\r\n    LGAudioBiquadFilter.desc = \"Audio filter\";\r\n    LiteGraph.registerNodeType(\"audio/biquadfilter\", LGAudioBiquadFilter);\r\n\r\n    function LGAudioOscillatorNode() {\r\n        //default\r\n        this.properties = {\r\n            frequency: 440,\r\n            detune: 0,\r\n            type: \"sine\"\r\n        };\r\n        this.addProperty(\"type\", \"sine\", \"enum\", {\r\n            values: [\"sine\", \"square\", \"sawtooth\", \"triangle\", \"custom\"]\r\n        });\r\n\r\n        //create node\r\n        this.audionode = LGAudio.getAudioContext().createOscillator();\r\n\r\n        //slots\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioOscillatorNode.prototype.onStart = function() {\r\n        if (!this.audionode.started) {\r\n            this.audionode.started = true;\r\n            try {\r\n                this.audionode.start();\r\n            } catch (err) {}\r\n        }\r\n    };\r\n\r\n    LGAudioOscillatorNode.prototype.onStop = function() {\r\n        if (this.audionode.started) {\r\n            this.audionode.started = false;\r\n            this.audionode.stop();\r\n        }\r\n    };\r\n\r\n    LGAudioOscillatorNode.prototype.onPause = function() {\r\n        this.onStop();\r\n    };\r\n\r\n    LGAudioOscillatorNode.prototype.onUnpause = function() {\r\n        this.onStart();\r\n    };\r\n\r\n    LGAudioOscillatorNode.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n\r\n        for (var i = 0; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n            if (input.link == null) {\r\n                continue;\r\n            }\r\n            var v = this.getInputData(i);\r\n            if (v !== undefined) {\r\n                this.audionode[input.name].value = v;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioOscillatorNode.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"frequency\", \"number\"],\r\n            [\"detune\", \"number\"],\r\n            [\"type\", \"string\"]\r\n        ];\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioOscillatorNode);\r\n\r\n    LGAudioOscillatorNode.title = \"Oscillator\";\r\n    LGAudioOscillatorNode.desc = \"Oscillator\";\r\n    LiteGraph.registerNodeType(\"audio/oscillator\", LGAudioOscillatorNode);\r\n\r\n    //*****************************************************\r\n\r\n    //EXTRA\r\n\r\n    function LGAudioVisualization() {\r\n        this.properties = {\r\n            continuous: true,\r\n            mark: -1\r\n        };\r\n\r\n        this.addInput(\"data\", \"array\");\r\n        this.addInput(\"mark\", \"number\");\r\n        this.size = [300, 200];\r\n        this._last_buffer = null;\r\n    }\r\n\r\n    LGAudioVisualization.prototype.onExecute = function() {\r\n        this._last_buffer = this.getInputData(0);\r\n        var v = this.getInputData(1);\r\n        if (v !== undefined) {\r\n            this.properties.mark = v;\r\n        }\r\n        this.setDirtyCanvas(true, false);\r\n    };\r\n\r\n    LGAudioVisualization.prototype.onDrawForeground = function(ctx) {\r\n        if (!this._last_buffer) {\r\n            return;\r\n        }\r\n\r\n        var buffer = this._last_buffer;\r\n\r\n        //delta represents how many samples we advance per pixel\r\n        var delta = buffer.length / this.size[0];\r\n        var h = this.size[1];\r\n\r\n        ctx.fillStyle = \"black\";\r\n        ctx.fillRect(0, 0, this.size[0], this.size[1]);\r\n        ctx.strokeStyle = \"white\";\r\n        ctx.beginPath();\r\n        var x = 0;\r\n\r\n        if (this.properties.continuous) {\r\n            ctx.moveTo(x, h);\r\n            for (var i = 0; i < buffer.length; i += delta) {\r\n                ctx.lineTo(x, h - (buffer[i | 0] / 255) * h);\r\n                x++;\r\n            }\r\n        } else {\r\n            for (var i = 0; i < buffer.length; i += delta) {\r\n                ctx.moveTo(x + 0.5, h);\r\n                ctx.lineTo(x + 0.5, h - (buffer[i | 0] / 255) * h);\r\n                x++;\r\n            }\r\n        }\r\n        ctx.stroke();\r\n\r\n        if (this.properties.mark >= 0) {\r\n            var samplerate = LGAudio.getAudioContext().sampleRate;\r\n            var binfreq = samplerate / buffer.length;\r\n            var x = (2 * (this.properties.mark / binfreq)) / delta;\r\n            if (x >= this.size[0]) {\r\n                x = this.size[0] - 1;\r\n            }\r\n            ctx.strokeStyle = \"red\";\r\n            ctx.beginPath();\r\n            ctx.moveTo(x, h);\r\n            ctx.lineTo(x, 0);\r\n            ctx.stroke();\r\n        }\r\n    };\r\n\r\n    LGAudioVisualization.title = \"Visualization\";\r\n    LGAudioVisualization.desc = \"Audio Visualization\";\r\n    LiteGraph.registerNodeType(\"audio/visualization\", LGAudioVisualization);\r\n\r\n    function LGAudioBandSignal() {\r\n        //default\r\n        this.properties = {\r\n            band: 440,\r\n            amplitude: 1\r\n        };\r\n\r\n        this.addInput(\"freqs\", \"array\");\r\n        this.addOutput(\"signal\", \"number\");\r\n    }\r\n\r\n    LGAudioBandSignal.prototype.onExecute = function() {\r\n        this._freqs = this.getInputData(0);\r\n        if (!this._freqs) {\r\n            return;\r\n        }\r\n\r\n        var band = this.properties.band;\r\n        var v = this.getInputData(1);\r\n        if (v !== undefined) {\r\n            band = v;\r\n        }\r\n\r\n        var samplerate = LGAudio.getAudioContext().sampleRate;\r\n        var binfreq = samplerate / this._freqs.length;\r\n        var index = 2 * (band / binfreq);\r\n        var v = 0;\r\n        if (index < 0) {\r\n            v = this._freqs[0];\r\n        }\r\n        if (index >= this._freqs.length) {\r\n            v = this._freqs[this._freqs.length - 1];\r\n        } else {\r\n            var pos = index | 0;\r\n            var v0 = this._freqs[pos];\r\n            var v1 = this._freqs[pos + 1];\r\n            var f = index - pos;\r\n            v = v0 * (1 - f) + v1 * f;\r\n        }\r\n\r\n        this.setOutputData(0, (v / 255) * this.properties.amplitude);\r\n    };\r\n\r\n    LGAudioBandSignal.prototype.onGetInputs = function() {\r\n        return [[\"band\", \"number\"]];\r\n    };\r\n\r\n    LGAudioBandSignal.title = \"Signal\";\r\n    LGAudioBandSignal.desc = \"extract the signal of some frequency\";\r\n    LiteGraph.registerNodeType(\"audio/signal\", LGAudioBandSignal);\r\n\r\n    function LGAudioScript() {\r\n        if (!LGAudioScript.default_code) {\r\n            var code = LGAudioScript.default_function.toString();\r\n            var index = code.indexOf(\"{\") + 1;\r\n            var index2 = code.lastIndexOf(\"}\");\r\n            LGAudioScript.default_code = code.substr(index, index2 - index);\r\n        }\r\n\r\n        //default\r\n        this.properties = {\r\n            code: LGAudioScript.default_code\r\n        };\r\n\r\n        //create node\r\n        var ctx = LGAudio.getAudioContext();\r\n        if (ctx.createScriptProcessor) {\r\n            this.audionode = ctx.createScriptProcessor(4096, 1, 1);\r\n        }\r\n        //buffer size, input channels, output channels\r\n        else {\r\n            console.warn(\"ScriptProcessorNode deprecated\");\r\n            this.audionode = ctx.createGain(); //bypass audio\r\n        }\r\n\r\n        this.processCode();\r\n        if (!LGAudioScript._bypass_function) {\r\n            LGAudioScript._bypass_function = this.audionode.onaudioprocess;\r\n        }\r\n\r\n        //slots\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioScript.prototype.onAdded = function(graph) {\r\n        if (graph.status == LGraph.STATUS_RUNNING) {\r\n            this.audionode.onaudioprocess = this._callback;\r\n        }\r\n    };\r\n\r\n    LGAudioScript[\"@code\"] = { widget: \"code\", type: \"code\" };\r\n\r\n    LGAudioScript.prototype.onStart = function() {\r\n        this.audionode.onaudioprocess = this._callback;\r\n    };\r\n\r\n    LGAudioScript.prototype.onStop = function() {\r\n        this.audionode.onaudioprocess = LGAudioScript._bypass_function;\r\n    };\r\n\r\n    LGAudioScript.prototype.onPause = function() {\r\n        this.audionode.onaudioprocess = LGAudioScript._bypass_function;\r\n    };\r\n\r\n    LGAudioScript.prototype.onUnpause = function() {\r\n        this.audionode.onaudioprocess = this._callback;\r\n    };\r\n\r\n    LGAudioScript.prototype.onExecute = function() {\r\n        //nothing! because we need an onExecute to receive onStart... fix that\r\n    };\r\n\r\n    LGAudioScript.prototype.onRemoved = function() {\r\n        this.audionode.onaudioprocess = LGAudioScript._bypass_function;\r\n    };\r\n\r\n    LGAudioScript.prototype.processCode = function() {\r\n        try {\r\n            var func = new Function(\"properties\", this.properties.code);\r\n            this._script = new func(this.properties);\r\n            this._old_code = this.properties.code;\r\n            this._callback = this._script.onaudioprocess;\r\n        } catch (err) {\r\n            console.error(\"Error in onaudioprocess code\", err);\r\n            this._callback = LGAudioScript._bypass_function;\r\n            this.audionode.onaudioprocess = this._callback;\r\n        }\r\n    };\r\n\r\n    LGAudioScript.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"code\") {\r\n            this.properties.code = value;\r\n            this.processCode();\r\n            if (this.graph && this.graph.status == LGraph.STATUS_RUNNING) {\r\n                this.audionode.onaudioprocess = this._callback;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioScript.default_function = function() {\r\n        this.onaudioprocess = function(audioProcessingEvent) {\r\n            // The input buffer is the song we loaded earlier\r\n            var inputBuffer = audioProcessingEvent.inputBuffer;\r\n\r\n            // The output buffer contains the samples that will be modified and played\r\n            var outputBuffer = audioProcessingEvent.outputBuffer;\r\n\r\n            // Loop through the output channels (in this case there is only one)\r\n            for (\r\n                var channel = 0;\r\n                channel < outputBuffer.numberOfChannels;\r\n                channel++\r\n            ) {\r\n                var inputData = inputBuffer.getChannelData(channel);\r\n                var outputData = outputBuffer.getChannelData(channel);\r\n\r\n                // Loop through the 4096 samples\r\n                for (var sample = 0; sample < inputBuffer.length; sample++) {\r\n                    // make output equal to the same as the input\r\n                    outputData[sample] = inputData[sample];\r\n                }\r\n            }\r\n        };\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioScript);\r\n\r\n    LGAudioScript.title = \"Script\";\r\n    LGAudioScript.desc = \"apply script to signal\";\r\n    LiteGraph.registerNodeType(\"audio/script\", LGAudioScript);\r\n\r\n    function LGAudioDestination() {\r\n        this.audionode = LGAudio.getAudioContext().destination;\r\n        this.addInput(\"in\", \"audio\");\r\n    }\r\n\r\n    LGAudioDestination.title = \"Destination\";\r\n    LGAudioDestination.desc = \"Audio output\";\r\n    LiteGraph.registerNodeType(\"audio/destination\", LGAudioDestination);\r\n})(this);\r\n\n//event related nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    function LGWebSocket() {\r\n        this.size = [60, 20];\r\n        this.addInput(\"send\", LiteGraph.ACTION);\r\n        this.addOutput(\"received\", LiteGraph.EVENT);\r\n        this.addInput(\"in\", 0);\r\n        this.addOutput(\"out\", 0);\r\n        this.properties = {\r\n            url: \"\",\r\n            room: \"lgraph\", //allows to filter messages,\r\n            only_send_changes: true\r\n        };\r\n        this._ws = null;\r\n        this._last_sent_data = [];\r\n        this._last_received_data = [];\r\n    }\r\n\r\n    LGWebSocket.title = \"WebSocket\";\r\n    LGWebSocket.desc = \"Send data through a websocket\";\r\n\r\n    LGWebSocket.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"url\") {\r\n            this.connectSocket();\r\n        }\r\n    };\r\n\r\n    LGWebSocket.prototype.onExecute = function() {\r\n        if (!this._ws && this.properties.url) {\r\n            this.connectSocket();\r\n        }\r\n\r\n        if (!this._ws || this._ws.readyState != WebSocket.OPEN) {\r\n            return;\r\n        }\r\n\r\n        var room = this.properties.room;\r\n        var only_changes = this.properties.only_send_changes;\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var data = this.getInputData(i);\r\n            if (data == null) {\r\n                continue;\r\n            }\r\n            var json;\r\n            try {\r\n                json = JSON.stringify({\r\n                    type: 0,\r\n                    room: room,\r\n                    channel: i,\r\n                    data: data\r\n                });\r\n            } catch (err) {\r\n                continue;\r\n            }\r\n            if (only_changes && this._last_sent_data[i] == json) {\r\n                continue;\r\n            }\r\n\r\n            this._last_sent_data[i] = json;\r\n            this._ws.send(json);\r\n        }\r\n\r\n        for (var i = 1; i < this.outputs.length; ++i) {\r\n            this.setOutputData(i, this._last_received_data[i]);\r\n        }\r\n\r\n        if (this.boxcolor == \"#AFA\") {\r\n            this.boxcolor = \"#6C6\";\r\n        }\r\n    };\r\n\r\n    LGWebSocket.prototype.connectSocket = function() {\r\n        var that = this;\r\n        var url = this.properties.url;\r\n        if (url.substr(0, 2) != \"ws\") {\r\n            url = \"ws://\" + url;\r\n        }\r\n        this._ws = new WebSocket(url);\r\n        this._ws.onopen = function() {\r\n            console.log(\"ready\");\r\n            that.boxcolor = \"#6C6\";\r\n        };\r\n        this._ws.onmessage = function(e) {\r\n            that.boxcolor = \"#AFA\";\r\n            var data = JSON.parse(e.data);\r\n            if (data.room && data.room != that.properties.room) {\r\n                return;\r\n            }\r\n            if (data.type == 1) {\r\n                if (\r\n                    data.data.object_class &&\r\n                    LiteGraph[data.data.object_class]\r\n                ) {\r\n                    var obj = null;\r\n                    try {\r\n                        obj = new LiteGraph[data.data.object_class](data.data);\r\n                        that.triggerSlot(0, obj);\r\n                    } catch (err) {\r\n                        return;\r\n                    }\r\n                } else {\r\n                    that.triggerSlot(0, data.data);\r\n                }\r\n            } else {\r\n                that._last_received_data[data.channel || 0] = data.data;\r\n            }\r\n        };\r\n        this._ws.onerror = function(e) {\r\n            console.log(\"couldnt connect to websocket\");\r\n            that.boxcolor = \"#E88\";\r\n        };\r\n        this._ws.onclose = function(e) {\r\n            console.log(\"connection closed\");\r\n            that.boxcolor = \"#000\";\r\n        };\r\n    };\r\n\r\n    LGWebSocket.prototype.send = function(data) {\r\n        if (!this._ws || this._ws.readyState != WebSocket.OPEN) {\r\n            return;\r\n        }\r\n        this._ws.send(JSON.stringify({ type: 1, msg: data }));\r\n    };\r\n\r\n    LGWebSocket.prototype.onAction = function(action, param) {\r\n        if (!this._ws || this._ws.readyState != WebSocket.OPEN) {\r\n            return;\r\n        }\r\n        this._ws.send({\r\n            type: 1,\r\n            room: this.properties.room,\r\n            action: action,\r\n            data: param\r\n        });\r\n    };\r\n\r\n    LGWebSocket.prototype.onGetInputs = function() {\r\n        return [[\"in\", 0]];\r\n    };\r\n\r\n    LGWebSocket.prototype.onGetOutputs = function() {\r\n        return [[\"out\", 0]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"network/websocket\", LGWebSocket);\r\n\r\n    //It is like a websocket but using the SillyServer.js server that bounces packets back to all clients connected:\r\n    //For more information: https://github.com/jagenjo/SillyServer.js\r\n\r\n    function LGSillyClient() {\r\n        //this.size = [60,20];\r\n        this.room_widget = this.addWidget(\r\n            \"text\",\r\n            \"Room\",\r\n            \"lgraph\",\r\n            this.setRoom.bind(this)\r\n        );\r\n        this.addWidget(\r\n            \"button\",\r\n            \"Reconnect\",\r\n            null,\r\n            this.connectSocket.bind(this)\r\n        );\r\n\r\n        this.addInput(\"send\", LiteGraph.ACTION);\r\n        this.addOutput(\"received\", LiteGraph.EVENT);\r\n        this.addInput(\"in\", 0);\r\n        this.addOutput(\"out\", 0);\r\n        this.properties = {\r\n            url: \"tamats.com:55000\",\r\n            room: \"lgraph\",\r\n            only_send_changes: true\r\n        };\r\n\r\n        this._server = null;\r\n        this.connectSocket();\r\n        this._last_sent_data = [];\r\n        this._last_received_data = [];\r\n\r\n\t\tif(typeof(SillyClient) == \"undefined\")\r\n\t\t\tconsole.warn(\"remember to add SillyClient.js to your project: https://tamats.com/projects/sillyserver/src/sillyclient.js\");\r\n    }\r\n\r\n    LGSillyClient.title = \"SillyClient\";\r\n    LGSillyClient.desc = \"Connects to SillyServer to broadcast messages\";\r\n\r\n    LGSillyClient.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"room\") {\r\n            this.room_widget.value = value;\r\n        }\r\n        this.connectSocket();\r\n    };\r\n\r\n    LGSillyClient.prototype.setRoom = function(room_name) {\r\n        this.properties.room = room_name;\r\n        this.room_widget.value = room_name;\r\n        this.connectSocket();\r\n    };\r\n\r\n    //force label names\r\n    LGSillyClient.prototype.onDrawForeground = function() {\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var slot = this.inputs[i];\r\n            slot.label = \"in_\" + i;\r\n        }\r\n        for (var i = 1; i < this.outputs.length; ++i) {\r\n            var slot = this.outputs[i];\r\n            slot.label = \"out_\" + i;\r\n        }\r\n    };\r\n\r\n    LGSillyClient.prototype.onExecute = function() {\r\n        if (!this._server || !this._server.is_connected) {\r\n            return;\r\n        }\r\n\r\n        var only_send_changes = this.properties.only_send_changes;\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var data = this.getInputData(i);\r\n\t\t\tvar prev_data = this._last_sent_data[i];\r\n            if (data != null) {\r\n                if (only_send_changes)\r\n\t\t\t\t{\t\r\n\t\t\t\t\tvar is_equal = true;\r\n\t\t\t\t\tif( data && data.length && prev_data && prev_data.length == data.length && data.constructor !== String)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfor(var j = 0; j < data.length; ++j)\r\n\t\t\t\t\t\t\tif( prev_data[j] != data[j] )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tis_equal = false;\r\n\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if(this._last_sent_data[i] != data)\r\n\t\t\t\t\t\tis_equal = false;\r\n\t\t\t\t\tif(is_equal)\r\n\t\t\t\t\t\t\tcontinue;\r\n                }\r\n                this._server.sendMessage({ type: 0, channel: i, data: data });\r\n\t\t\t\tif( data.length && data.constructor !== String )\r\n\t\t\t\t{\r\n\t\t\t\t\tif( this._last_sent_data[i] )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis._last_sent_data[i].length = data.length;\r\n\t\t\t\t\t\tfor(var j = 0; j < data.length; ++j)\r\n\t\t\t\t\t\t\tthis._last_sent_data[i][j] = data[j];\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse //create\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif(data.constructor === Array)\r\n\t\t\t\t\t\t\tthis._last_sent_data[i] = data.concat();\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\tthis._last_sent_data[i] = new data.constructor( data );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t                this._last_sent_data[i] = data; //should be cloned\r\n            }\r\n        }\r\n\r\n        for (var i = 1; i < this.outputs.length; ++i) {\r\n            this.setOutputData(i, this._last_received_data[i]);\r\n        }\r\n\r\n        if (this.boxcolor == \"#AFA\") {\r\n            this.boxcolor = \"#6C6\";\r\n        }\r\n    };\r\n\r\n    LGSillyClient.prototype.connectSocket = function() {\r\n        var that = this;\r\n        if (typeof SillyClient == \"undefined\") {\r\n            if (!this._error) {\r\n                console.error(\r\n                    \"SillyClient node cannot be used, you must include SillyServer.js\"\r\n                );\r\n            }\r\n            this._error = true;\r\n            return;\r\n        }\r\n\r\n        this._server = new SillyClient();\r\n        this._server.on_ready = function() {\r\n            console.log(\"ready\");\r\n            that.boxcolor = \"#6C6\";\r\n        };\r\n        this._server.on_message = function(id, msg) {\r\n            var data = null;\r\n            try {\r\n                data = JSON.parse(msg);\r\n            } catch (err) {\r\n                return;\r\n            }\r\n\r\n            if (data.type == 1) {\r\n                //EVENT slot\r\n                if (\r\n                    data.data.object_class &&\r\n                    LiteGraph[data.data.object_class]\r\n                ) {\r\n                    var obj = null;\r\n                    try {\r\n                        obj = new LiteGraph[data.data.object_class](data.data);\r\n                        that.triggerSlot(0, obj);\r\n                    } catch (err) {\r\n                        return;\r\n                    }\r\n                } else {\r\n                    that.triggerSlot(0, data.data);\r\n                }\r\n            } //for FLOW slots\r\n            else {\r\n                that._last_received_data[data.channel || 0] = data.data;\r\n            }\r\n            that.boxcolor = \"#AFA\";\r\n        };\r\n        this._server.on_error = function(e) {\r\n            console.log(\"couldnt connect to websocket\");\r\n            that.boxcolor = \"#E88\";\r\n        };\r\n        this._server.on_close = function(e) {\r\n            console.log(\"connection closed\");\r\n            that.boxcolor = \"#000\";\r\n        };\r\n\r\n        if (this.properties.url && this.properties.room) {\r\n            try {\r\n                this._server.connect(this.properties.url, this.properties.room);\r\n            } catch (err) {\r\n                console.error(\"SillyServer error: \" + err);\r\n                this._server = null;\r\n                return;\r\n            }\r\n            this._final_url = this.properties.url + \"/\" + this.properties.room;\r\n        }\r\n    };\r\n\r\n    LGSillyClient.prototype.send = function(data) {\r\n        if (!this._server || !this._server.is_connected) {\r\n            return;\r\n        }\r\n        this._server.sendMessage({ type: 1, data: data });\r\n    };\r\n\r\n    LGSillyClient.prototype.onAction = function(action, param) {\r\n        if (!this._server || !this._server.is_connected) {\r\n            return;\r\n        }\r\n        this._server.sendMessage({ type: 1, action: action, data: param });\r\n    };\r\n\r\n    LGSillyClient.prototype.onGetInputs = function() {\r\n        return [[\"in\", 0]];\r\n    };\r\n\r\n    LGSillyClient.prototype.onGetOutputs = function() {\r\n        return [[\"out\", 0]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"network/sillyclient\", LGSillyClient);\r\n\r\n//HTTP Request\r\nfunction HTTPRequestNode() {\r\n\tvar that = this;\r\n\tthis.addInput(\"request\", LiteGraph.ACTION);\r\n\tthis.addInput(\"url\", \"string\");\r\n\tthis.addProperty(\"url\", \"\");\r\n\tthis.addOutput(\"ready\", LiteGraph.EVENT);\r\n    this.addOutput(\"data\", \"string\");\r\n\tthis.addWidget(\"button\", \"Fetch\", null, this.fetch.bind(this));\r\n\tthis._data = null;\r\n\tthis._fetching = null;\r\n}\r\n\r\nHTTPRequestNode.title = \"HTTP Request\";\r\nHTTPRequestNode.desc = \"Fetch data through HTTP\";\r\n\r\nHTTPRequestNode.prototype.fetch = function()\r\n{\r\n\tvar url = this.properties.url;\r\n\tif(!url)\r\n\t\treturn;\r\n\r\n\tthis.boxcolor = \"#FF0\";\r\n\tvar that = this;\r\n\tthis._fetching = fetch(url)\r\n\t.then(resp=>{\r\n\t\tif(!resp.ok)\r\n\t\t{\r\n\t\t\tthis.boxcolor = \"#F00\";\r\n\t\t\tthat.trigger(\"error\");\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.boxcolor = \"#0F0\";\r\n\t\t\treturn resp.text();\r\n\t\t}\r\n\t})\r\n\t.then(data=>{\r\n\t\tthat._data = data;\r\n\t\tthat._fetching = null;\r\n\t\tthat.trigger(\"ready\");\r\n\t});\r\n}\r\n\r\nHTTPRequestNode.prototype.onAction = function(evt)\r\n{\r\n\tif(evt == \"request\")\r\n\t\tthis.fetch();\r\n}\r\n\r\nHTTPRequestNode.prototype.onExecute = function() {\r\n\tthis.setOutputData(1, this._data);\r\n};\r\n\r\nHTTPRequestNode.prototype.onGetOutputs = function() {\r\n\treturn [[\"error\",LiteGraph.EVENT]];\r\n}\r\n\r\nLiteGraph.registerNodeType(\"network/httprequest\", HTTPRequestNode);\r\n\r\n\r\n\t\r\n})(this);\r\n\n"
  },
  {
    "path": "build/litegraph_mini.js",
    "content": "//packer version\n\n\n(function(global) {\n    // *************************************************************\n    //   LiteGraph CLASS                                     *******\n    // *************************************************************\n\n    /**\n     * The Global Scope. It contains all the registered node classes.\n     *\n     * @class LiteGraph\n     * @constructor\n     */\n\n    var LiteGraph = (global.LiteGraph = {\n        VERSION: 0.4,\n\n        CANVAS_GRID_SIZE: 10,\n\n        NODE_TITLE_HEIGHT: 30,\n        NODE_TITLE_TEXT_Y: 20,\n        NODE_SLOT_HEIGHT: 20,\n        NODE_WIDGET_HEIGHT: 20,\n        NODE_WIDTH: 140,\n        NODE_MIN_WIDTH: 50,\n        NODE_COLLAPSED_RADIUS: 10,\n        NODE_COLLAPSED_WIDTH: 80,\n        NODE_TITLE_COLOR: \"#999\",\n        NODE_SELECTED_TITLE_COLOR: \"#FFF\",\n        NODE_TEXT_SIZE: 14,\n        NODE_TEXT_COLOR: \"#AAA\",\n        NODE_SUBTEXT_SIZE: 12,\n        NODE_DEFAULT_COLOR: \"#333\",\n        NODE_DEFAULT_BGCOLOR: \"#353535\",\n        NODE_DEFAULT_BOXCOLOR: \"#666\",\n        NODE_DEFAULT_SHAPE: \"box\",\n        NODE_BOX_OUTLINE_COLOR: \"#FFF\",\n        DEFAULT_SHADOW_COLOR: \"rgba(0,0,0,0.5)\",\n        DEFAULT_GROUP_FONT: 24,\n\n        WIDGET_BGCOLOR: \"#222\",\n        WIDGET_OUTLINE_COLOR: \"#666\",\n        WIDGET_TEXT_COLOR: \"#DDD\",\n        WIDGET_SECONDARY_TEXT_COLOR: \"#999\",\n\n        LINK_COLOR: \"#9A9\",\n        EVENT_LINK_COLOR: \"#A86\",\n        CONNECTING_LINK_COLOR: \"#AFA\",\n\n        MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops\n        DEFAULT_POSITION: [100, 100], //default node position\n        VALID_SHAPES: [\"default\", \"box\", \"round\", \"card\"], //,\"circle\"\n\n        //shapes are used for nodes but also for slots\n        BOX_SHAPE: 1,\n        ROUND_SHAPE: 2,\n        CIRCLE_SHAPE: 3,\n        CARD_SHAPE: 4,\n        ARROW_SHAPE: 5,\n        GRID_SHAPE: 6, // intended for slot arrays\n\n        //enums\n        INPUT: 1,\n        OUTPUT: 2,\n\n        EVENT: -1, //for outputs\n        ACTION: -1, //for inputs\n\n        NODE_MODES: [\"Always\", \"On Event\", \"Never\", \"On Trigger\"], // helper, will add \"On Request\" and more in the future\n        NODE_MODES_COLORS:[\"#666\",\"#422\",\"#333\",\"#224\",\"#626\"], // use with node_box_coloured_by_mode\n        ALWAYS: 0,\n        ON_EVENT: 1,\n        NEVER: 2,\n        ON_TRIGGER: 3,\n\n        UP: 1,\n        DOWN: 2,\n        LEFT: 3,\n        RIGHT: 4,\n        CENTER: 5,\n\n        LINK_RENDER_MODES: [\"Straight\", \"Linear\", \"Spline\"], // helper\n        STRAIGHT_LINK: 0,\n        LINEAR_LINK: 1,\n        SPLINE_LINK: 2,\n\n        NORMAL_TITLE: 0,\n        NO_TITLE: 1,\n        TRANSPARENT_TITLE: 2,\n        AUTOHIDE_TITLE: 3,\n        VERTICAL_LAYOUT: \"vertical\", // arrange nodes vertically\n\n        proxy: null, //used to redirect calls\n        node_images_path: \"\",\n\n        debug: false,\n        catch_exceptions: true,\n        throw_errors: true,\n        allow_scripts: false, //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits\n        use_deferred_actions: true, //executes actions during the graph execution flow\n        registered_node_types: {}, //nodetypes by string\n        node_types_by_file_extension: {}, //used for dropping files in the canvas\n        Nodes: {}, //node types by classname\n\t\tGlobals: {}, //used to store vars between graphs\n\n        searchbox_extras: {}, //used to add extra features to the search box\n        auto_sort_node_types: false, // [true!] If set to true, will automatically sort node types / categories in the context menus\n\t\t\n\t\tnode_box_coloured_when_on: false, // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback\n        node_box_coloured_by_mode: false, // [true!] nodebox based on node mode, visual feedback\n        \n        dialog_close_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false\n        dialog_close_on_mouse_leave_delay: 500,\n        \n        shift_click_do_break_link_from: false, // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys\n        click_do_break_link_to: false, // [false!]prefer false, way too easy to break links\n        \n        search_hide_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false\n        search_filter_enabled: false, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out]\n        search_show_all_on_open: true, // [true!] opens the results list when opening the search widget\n        \n        auto_load_slot_types: false, // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out]\n        \n\t\t// set these values if not using auto_load_slot_types\n        registered_slot_in_types: {}, // slot types for nodeclass\n        registered_slot_out_types: {}, // slot types for nodeclass\n        slot_types_in: [], // slot types IN\n        slot_types_out: [], // slot types OUT\n        slot_types_default_in: [], // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search\n\t\tslot_types_default_out: [], // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search\n\t\t\n\t\talt_drag_do_clone_nodes: false, // [true!] very handy, ALT click to clone and drag the new node\n\n\t\tdo_add_triggers_slots: false, // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this\n\t\t\n\t\tallow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentially, one by one\n\n\t\tmiddle_click_slot_add_default_node: false, //[true!] allows to create and connect a ndoe clicking with the third button (wheel)\n\t\t\n\t\trelease_link_on_empty_shows_menu: false, //[true!] dragging a link to empty space will open a menu, add from list, search or defaults\n\t\t\n        pointerevents_method: \"mouse\", // \"mouse\"|\"pointer\" use mouse for retrocompatibility issues? (none found @ now)\n        // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary)\n\n        ctrl_shift_v_paste_connect_unselected_outputs: false, //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes\n\n        // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers.\n        // use this if you must have node IDs that are unique across all graphs and subgraphs.\n        use_uuids: false,\n\n        /**\n         * Register a node class so it can be listed when the user wants to create a new one\n         * @method registerNodeType\n         * @param {String} type name of the node and path\n         * @param {Class} base_class class containing the structure of a node\n         */\n\n        registerNodeType: function(type, base_class) {\n            if (!base_class.prototype) {\n                throw \"Cannot register a simple object, it must be a class with a prototype\";\n            }\n            base_class.type = type;\n\n            if (LiteGraph.debug) {\n                console.log(\"Node registered: \" + type);\n            }\n\n            const classname = base_class.name;\n\n            const pos = type.lastIndexOf(\"/\");\n            base_class.category = type.substring(0, pos);\n\n            if (!base_class.title) {\n                base_class.title = classname;\n            }\n\n            //extend class\n            for (var i in LGraphNode.prototype) {\n                if (!base_class.prototype[i]) {\n                    base_class.prototype[i] = LGraphNode.prototype[i];\n                }\n            }\n\n            const prev = this.registered_node_types[type];\n            if(prev) {\n                console.log(\"replacing node type: \" + type);\n            }\n            if( !Object.prototype.hasOwnProperty.call( base_class.prototype, \"shape\") ) {\n                Object.defineProperty(base_class.prototype, \"shape\", {\n                    set: function(v) {\n                        switch (v) {\n                            case \"default\":\n                                delete this._shape;\n                                break;\n                            case \"box\":\n                                this._shape = LiteGraph.BOX_SHAPE;\n                                break;\n                            case \"round\":\n                                this._shape = LiteGraph.ROUND_SHAPE;\n                                break;\n                            case \"circle\":\n                                this._shape = LiteGraph.CIRCLE_SHAPE;\n                                break;\n                            case \"card\":\n                                this._shape = LiteGraph.CARD_SHAPE;\n                                break;\n                            default:\n                                this._shape = v;\n                        }\n                    },\n                    get: function() {\n                        return this._shape;\n                    },\n                    enumerable: true,\n                    configurable: true\n                });\n                \n\n                //used to know which nodes to create when dragging files to the canvas\n                if (base_class.supported_extensions) {\n                    for (let i in base_class.supported_extensions) {\n                        const ext = base_class.supported_extensions[i];\n                        if(ext && ext.constructor === String) {\n                            this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class;\n                        }\n                    }\n                }\n            }\n\n            this.registered_node_types[type] = base_class;\n            if (base_class.constructor.name) {\n                this.Nodes[classname] = base_class;\n            }\n            if (LiteGraph.onNodeTypeRegistered) {\n                LiteGraph.onNodeTypeRegistered(type, base_class);\n            }\n            if (prev && LiteGraph.onNodeTypeReplaced) {\n                LiteGraph.onNodeTypeReplaced(type, base_class, prev);\n            }\n\n            //warnings\n            if (base_class.prototype.onPropertyChange) {\n                console.warn(\n                    \"LiteGraph node class \" +\n                        type +\n                        \" has onPropertyChange method, it must be called onPropertyChanged with d at the end\"\n                );\n            }\n            \n            // TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types\n            if (this.auto_load_slot_types) {\n                new base_class(base_class.title || \"tmpnode\");\n            }\n        },\n\n        /**\n         * removes a node type from the system\n         * @method unregisterNodeType\n         * @param {String|Object} type name of the node or the node constructor itself\n         */\n        unregisterNodeType: function(type) {\n            const base_class =\n                type.constructor === String\n                    ? this.registered_node_types[type]\n                    : type;\n            if (!base_class) {\n                throw \"node type not found: \" + type;\n            }\n            delete this.registered_node_types[base_class.type];\n            if (base_class.constructor.name) {\n                delete this.Nodes[base_class.constructor.name];\n            }\n        },\n\n        /**\n        * Save a slot type and his node\n        * @method registerSlotType\n        * @param {String|Object} type name of the node or the node constructor itself\n        * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, ..\n        */\n        registerNodeAndSlotType: function(type, slot_type, out){\n            out = out || false;\n            const base_class =\n                type.constructor === String &&\n                this.registered_node_types[type] !== \"anonymous\"\n                    ? this.registered_node_types[type]\n                    : type;\n\n            const class_type = base_class.constructor.type;\n\n            let allTypes = [];\n            if (typeof slot_type === \"string\") {\n                allTypes = slot_type.split(\",\");\n            } else if (slot_type == this.EVENT || slot_type == this.ACTION) {\n                allTypes = [\"_event_\"];\n            } else {\n                allTypes = [\"*\"];\n            }\n\n            for (let i = 0; i < allTypes.length; ++i) {\n                let slotType = allTypes[i];\n                if (slotType === \"\") {\n                    slotType = \"*\";\n                }\n                const registerTo = out\n                    ? \"registered_slot_out_types\"\n                    : \"registered_slot_in_types\";\n                if (this[registerTo][slotType] === undefined) {\n                    this[registerTo][slotType] = { nodes: [] };\n                }\n                if (!this[registerTo][slotType].nodes.includes(class_type)) {\n                    this[registerTo][slotType].nodes.push(class_type);\n                }\n\n                // check if is a new type\n                if (!out) {\n                    if (!this.slot_types_in.includes(slotType.toLowerCase())) {\n                        this.slot_types_in.push(slotType.toLowerCase());\n                        this.slot_types_in.sort();\n                    }\n                } else {\n                    if (!this.slot_types_out.includes(slotType.toLowerCase())) {\n                        this.slot_types_out.push(slotType.toLowerCase());\n                        this.slot_types_out.sort();\n                    }\n                }\n            }\n        },\n        \n        /**\n         * Create a new nodetype by passing an object with some properties\n         * like onCreate, inputs:Array, outputs:Array, properties, onExecute\n         * @method buildNodeClassFromObject\n         * @param {String} name node name with namespace (p.e.: 'math/sum')\n         * @param {Object} object methods expected onCreate, inputs, outputs, properties, onExecute\n         */\n         buildNodeClassFromObject: function(\n            name,\n            object\n        ) {\n            var ctor_code = \"\";\n            if(object.inputs)\n            for(var i=0; i < object.inputs.length; ++i)\n            {\n                var _name = object.inputs[i][0];\n                var _type = object.inputs[i][1];\n                if(_type && _type.constructor === String)\n                    _type = '\"'+_type+'\"';\n                ctor_code += \"this.addInput('\"+_name+\"',\"+_type+\");\\n\";\n            }\n            if(object.outputs)\n            for(var i=0; i < object.outputs.length; ++i)\n            {\n                var _name = object.outputs[i][0];\n                var _type = object.outputs[i][1];\n                if(_type && _type.constructor === String)\n                    _type = '\"'+_type+'\"';\n                ctor_code += \"this.addOutput('\"+_name+\"',\"+_type+\");\\n\";\n            }\n            if(object.properties)\n            for(var i in object.properties)\n            {\n                var prop = object.properties[i];\n                if(prop && prop.constructor === String)\n                    prop = '\"'+prop+'\"';\n                ctor_code += \"this.addProperty('\"+i+\"',\"+prop+\");\\n\";\n            }\n            ctor_code += \"if(this.onCreate)this.onCreate()\";\n            var classobj = Function(ctor_code);\n            for(var i in object)\n                if(i!=\"inputs\" && i!=\"outputs\" && i!=\"properties\")\n                    classobj.prototype[i] = object[i];\n            classobj.title = object.title || name.split(\"/\").pop();\n            classobj.desc = object.desc || \"Generated from object\";\n            this.registerNodeType(name, classobj);\n            return classobj;\n        },\n        \n        /**\n         * 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.\n         * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output.\n         * @method wrapFunctionAsNode\n         * @param {String} name node name with namespace (p.e.: 'math/sum')\n         * @param {Function} func\n         * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type\n         * @param {String} return_type [optional] string with the return type, otherwise it will be generic\n         * @param {Object} properties [optional] properties to be configurable\n         */\n        wrapFunctionAsNode: function(\n            name,\n            func,\n            param_types,\n            return_type,\n            properties\n        ) {\n            var params = Array(func.length);\n            var code = \"\";\n            if(param_types !== null) //null means no inputs\n            {\n                var names = LiteGraph.getParameterNames(func);\n                for (var i = 0; i < names.length; ++i) {\n                    var type = 0;\n                    if(param_types)\n                    {\n                        //type = param_types[i] != null ? \"'\" + param_types[i] + \"'\" : \"0\";\n                        if( param_types[i] != null && param_types[i].constructor === String )\n                            type = \"'\" + param_types[i] + \"'\" ;\n                        else if( param_types[i] != null )\n                            type = param_types[i];\n                    } \n                    code +=\n                        \"this.addInput('\" +\n                        names[i] +\n                        \"',\" +\n                        type +\n                        \");\\n\";\n                }\n            }\n            if(return_type !== null) //null means no output\n            code +=\n                \"this.addOutput('out',\" +\n                (return_type != null ? (return_type.constructor === String ? \"'\" + return_type + \"'\" : return_type) : 0) +\n                \");\\n\";\n            if (properties) {\n                code +=\n                    \"this.properties = \" + JSON.stringify(properties) + \";\\n\";\n            }\n            var classobj = Function(code);\n            classobj.title = name.split(\"/\").pop();\n            classobj.desc = \"Generated from \" + func.name;\n            classobj.prototype.onExecute = function onExecute() {\n                for (var i = 0; i < params.length; ++i) {\n                    params[i] = this.getInputData(i);\n                }\n                var r = func.apply(this, params);\n                this.setOutputData(0, r);\n            };\n            this.registerNodeType(name, classobj);\n            return classobj;\n        },\n\n        /**\n         * Removes all previously registered node's types\n         */\n        clearRegisteredTypes: function() {\n            this.registered_node_types = {};\n            this.node_types_by_file_extension = {};\n            this.Nodes = {};\n            this.searchbox_extras = {};\n        },\n\n        /**\n         * Adds this method to all nodetypes, existing and to be created\n         * (You can add it to LGraphNode.prototype but then existing node types wont have it)\n         * @method addNodeMethod\n         * @param {Function} func\n         */\n        addNodeMethod: function(name, func) {\n            LGraphNode.prototype[name] = func;\n            for (var i in this.registered_node_types) {\n                var type = this.registered_node_types[i];\n                if (type.prototype[name]) {\n                    type.prototype[\"_\" + name] = type.prototype[name];\n                } //keep old in case of replacing\n                type.prototype[name] = func;\n            }\n        },\n\n        /**\n         * Create a node of a given type with a name. The node is not attached to any graph yet.\n         * @method createNode\n         * @param {String} type full name of the node class. p.e. \"math/sin\"\n         * @param {String} name a name to distinguish from other nodes\n         * @param {Object} options to set options\n         */\n\n        createNode: function(type, title, options) {\n            var base_class = this.registered_node_types[type];\n            if (!base_class) {\n                if (LiteGraph.debug) {\n                    console.log(\n                        'GraphNode type \"' + type + '\" not registered.'\n                    );\n                }\n                return null;\n            }\n\n            var prototype = base_class.prototype || base_class;\n\n            title = title || base_class.title || type;\n\n            var node = null;\n\n            if (LiteGraph.catch_exceptions) {\n                try {\n                    node = new base_class(title);\n                } catch (err) {\n                    console.error(err);\n                    return null;\n                }\n            } else {\n                node = new base_class(title);\n            }\n\n            node.type = type;\n\n            if (!node.title && title) {\n                node.title = title;\n            }\n            if (!node.properties) {\n                node.properties = {};\n            }\n            if (!node.properties_info) {\n                node.properties_info = [];\n            }\n            if (!node.flags) {\n                node.flags = {};\n            }\n            if (!node.size) {\n                node.size = node.computeSize();\n\t\t\t\t//call onresize?\n            }\n            if (!node.pos) {\n                node.pos = LiteGraph.DEFAULT_POSITION.concat();\n            }\n            if (!node.mode) {\n                node.mode = LiteGraph.ALWAYS;\n            }\n\n            //extra options\n            if (options) {\n                for (var i in options) {\n                    node[i] = options[i];\n                }\n            }\n\n\t\t\t// callback\n            if ( node.onNodeCreated ) {\n                node.onNodeCreated();\n            }\n            \n            return node;\n        },\n\n        /**\n         * Returns a registered node type with a given name\n         * @method getNodeType\n         * @param {String} type full name of the node class. p.e. \"math/sin\"\n         * @return {Class} the node class\n         */\n        getNodeType: function(type) {\n            return this.registered_node_types[type];\n        },\n\n        /**\n         * Returns a list of node types matching one category\n         * @method getNodeType\n         * @param {String} category category name\n         * @return {Array} array with all the node classes\n         */\n\n        getNodeTypesInCategory: function(category, filter) {\n            var r = [];\n            for (var i in this.registered_node_types) {\n                var type = this.registered_node_types[i];\n                if (type.filter != filter) {\n                    continue;\n                }\n\n                if (category == \"\") {\n                    if (type.category == null) {\n                        r.push(type);\n                    }\n                } else if (type.category == category) {\n                    r.push(type);\n                }\n            }\n\n            if (this.auto_sort_node_types) {\n                r.sort(function(a,b){return a.title.localeCompare(b.title)});\n            }\n\n            return r;\n        },\n\n        /**\n         * Returns a list with all the node type categories\n         * @method getNodeTypesCategories\n         * @param {String} filter only nodes with ctor.filter equal can be shown\n         * @return {Array} array with all the names of the categories\n         */\n        getNodeTypesCategories: function( filter ) {\n            var categories = { \"\": 1 };\n            for (var i in this.registered_node_types) {\n\t\t\t\tvar type = this.registered_node_types[i];\n                if ( type.category && !type.skip_list )\n                {\n\t\t\t\t\tif(type.filter != filter)\n\t\t\t\t\t\tcontinue;\n                    categories[type.category] = 1;\n                }\n            }\n            var result = [];\n            for (var i in categories) {\n                result.push(i);\n            }\n            return this.auto_sort_node_types ? result.sort() : result;\n        },\n\n        //debug purposes: reloads all the js scripts that matches a wildcard\n        reloadNodes: function(folder_wildcard) {\n            var tmp = document.getElementsByTagName(\"script\");\n            //weird, this array changes by its own, so we use a copy\n            var script_files = [];\n            for (var i=0; i < tmp.length; i++) {\n                script_files.push(tmp[i]);\n            }\n\n            var docHeadObj = document.getElementsByTagName(\"head\")[0];\n            folder_wildcard = document.location.href + folder_wildcard;\n\n            for (var i=0; i < script_files.length; i++) {\n                var src = script_files[i].src;\n                if (\n                    !src ||\n                    src.substr(0, folder_wildcard.length) != folder_wildcard\n                ) {\n                    continue;\n                }\n\n                try {\n                    if (LiteGraph.debug) {\n                        console.log(\"Reloading: \" + src);\n                    }\n                    var dynamicScript = document.createElement(\"script\");\n                    dynamicScript.type = \"text/javascript\";\n                    dynamicScript.src = src;\n                    docHeadObj.appendChild(dynamicScript);\n                    docHeadObj.removeChild(script_files[i]);\n                } catch (err) {\n                    if (LiteGraph.throw_errors) {\n                        throw err;\n                    }\n                    if (LiteGraph.debug) {\n                        console.log(\"Error while reloading \" + src);\n                    }\n                }\n            }\n\n            if (LiteGraph.debug) {\n                console.log(\"Nodes reloaded\");\n            }\n        },\n\n        //separated just to improve if it doesn't work\n        cloneObject: function(obj, target) {\n            if (obj == null) {\n                return null;\n            }\n            var r = JSON.parse(JSON.stringify(obj));\n            if (!target) {\n                return r;\n            }\n\n            for (var i in r) {\n                target[i] = r[i];\n            }\n            return target;\n        },\n\n        /*\n         * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670\n         */\n        uuidv4: function() {\n            return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,a=>(a^Math.random()*16>>a/4).toString(16));\n        },\n\n        /**\n         * Returns if the types of two slots are compatible (taking into account wildcards, etc)\n         * @method isValidConnection\n         * @param {String} type_a\n         * @param {String} type_b\n         * @return {Boolean} true if they can be connected\n         */\n        isValidConnection: function(type_a, type_b) {\n\t\t\tif (type_a==\"\" || type_a===\"*\") type_a = 0;\n\t\t\tif (type_b==\"\" || type_b===\"*\") type_b = 0;\n            if (\n                !type_a //generic output\n                || !type_b // generic input\n                || type_a == type_b //same type (is valid for triggers)\n                || (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION)\n            ) {\n                return true;\n            }\n\n            // Enforce string type to handle toLowerCase call (-1 number not ok)\n            type_a = String(type_a);\n            type_b = String(type_b);\n            type_a = type_a.toLowerCase();\n            type_b = type_b.toLowerCase();\n\n            // For nodes supporting multiple connection types\n            if (type_a.indexOf(\",\") == -1 && type_b.indexOf(\",\") == -1) {\n                return type_a == type_b;\n            }\n\n            // Check all permutations to see if one is valid\n            var supported_types_a = type_a.split(\",\");\n            var supported_types_b = type_b.split(\",\");\n            for (var i = 0; i < supported_types_a.length; ++i) {\n                for (var j = 0; j < supported_types_b.length; ++j) {\n                    if(this.isValidConnection(supported_types_a[i],supported_types_b[j])){\n\t\t\t\t\t//if (supported_types_a[i] == supported_types_b[j]) {\n                        return true;\n                    }\n                }\n            }\n\n            return false;\n        },\n\n        /**\n         * Register a string in the search box so when the user types it it will recommend this node\n         * @method registerSearchboxExtra\n         * @param {String} node_type the node recommended\n         * @param {String} description text to show next to it\n         * @param {Object} data it could contain info of how the node should be configured\n         * @return {Boolean} true if they can be connected\n         */\n        registerSearchboxExtra: function(node_type, description, data) {\n            this.searchbox_extras[description.toLowerCase()] = {\n                type: node_type,\n                desc: description,\n                data: data\n            };\n        },\n\n        /**\n         * Wrapper to load files (from url using fetch or from file using FileReader)\n         * @method fetchFile\n         * @param {String|File|Blob} url the url of the file (or the file itself)\n         * @param {String} type an string to know how to fetch it: \"text\",\"arraybuffer\",\"json\",\"blob\"\n         * @param {Function} on_complete callback(data)\n         * @param {Function} on_error in case of an error\n         * @return {FileReader|Promise} returns the object used to \n         */\n\t\tfetchFile: function( url, type, on_complete, on_error ) {\n\t\t\tvar that = this;\n\t\t\tif(!url)\n\t\t\t\treturn null;\n\n\t\t\ttype = type || \"text\";\n\t\t\tif( url.constructor === String )\n\t\t\t{\n\t\t\t\tif (url.substr(0, 4) == \"http\" && LiteGraph.proxy) {\n\t\t\t\t\turl = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\n\t\t\t\t}\n\t\t\t\treturn fetch(url)\n\t\t\t\t.then(function(response) {\n\t\t\t\t\tif(!response.ok)\n\t\t\t\t\t\t throw new Error(\"File not found\"); //it will be catch below\n\t\t\t\t\tif(type == \"arraybuffer\")\n\t\t\t\t\t\treturn response.arrayBuffer();\n\t\t\t\t\telse if(type == \"text\" || type == \"string\")\n\t\t\t\t\t\treturn response.text();\n\t\t\t\t\telse if(type == \"json\")\n\t\t\t\t\t\treturn response.json();\n\t\t\t\t\telse if(type == \"blob\")\n\t\t\t\t\t\treturn response.blob();\n\t\t\t\t})\n\t\t\t\t.then(function(data) {\n\t\t\t\t\tif(on_complete)\n\t\t\t\t\t\ton_complete(data);\n\t\t\t\t})\n\t\t\t\t.catch(function(error) {\n\t\t\t\t\tconsole.error(\"error fetching file:\",url);\n\t\t\t\t\tif(on_error)\n\t\t\t\t\t\ton_error(error);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if( url.constructor === File || url.constructor === Blob)\n\t\t\t{\n\t\t\t\tvar reader = new FileReader();\n\t\t\t\treader.onload = function(e)\n\t\t\t\t{\n\t\t\t\t\tvar v = e.target.result;\n\t\t\t\t\tif( type == \"json\" )\n\t\t\t\t\t\tv = JSON.parse(v);\n\t\t\t\t\tif(on_complete)\n\t\t\t\t\t\ton_complete(v);\n\t\t\t\t}\n\t\t\t\tif(type == \"arraybuffer\")\n\t\t\t\t\treturn reader.readAsArrayBuffer(url);\n\t\t\t\telse if(type == \"text\" || type == \"json\")\n\t\t\t\t\treturn reader.readAsText(url);\n\t\t\t\telse if(type == \"blob\")\n\t\t\t\t\treturn reader.readAsBinaryString(url);\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n    });\n\n    //timer that works everywhere\n    if (typeof performance != \"undefined\") {\n        LiteGraph.getTime = performance.now.bind(performance);\n    } else if (typeof Date != \"undefined\" && Date.now) {\n        LiteGraph.getTime = Date.now.bind(Date);\n    } else if (typeof process != \"undefined\") {\n        LiteGraph.getTime = function() {\n            var t = process.hrtime();\n            return t[0] * 0.001 + t[1] * 1e-6;\n        };\n    } else {\n        LiteGraph.getTime = function getTime() {\n            return new Date().getTime();\n        };\n    }\n\n    //*********************************************************************************\n    // LGraph CLASS\n    //*********************************************************************************\n\n    /**\n     * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.\n\t * supported callbacks:\n\t\t+ onNodeAdded: when a new node is added to the graph\n\t\t+ onNodeRemoved: when a node inside this graph is removed\n\t\t+ onNodeConnectionChange: some connection has changed in the graph (connected or disconnected)\n     *\n     * @class LGraph\n     * @constructor\n     * @param {Object} o data from previous serialization [optional]\n     */\n\n    function LGraph(o) {\n        if (LiteGraph.debug) {\n            console.log(\"Graph created\");\n        }\n        this.list_of_graphcanvas = null;\n        this.clear();\n\n        if (o) {\n            this.configure(o);\n        }\n    }\n\n    global.LGraph = LiteGraph.LGraph = LGraph;\n\n    //default supported types\n    LGraph.supported_types = [\"number\", \"string\", \"boolean\"];\n\n    //used to know which types of connections support this graph (some graphs do not allow certain types)\n    LGraph.prototype.getSupportedTypes = function() {\n        return this.supported_types || LGraph.supported_types;\n    };\n\n    LGraph.STATUS_STOPPED = 1;\n    LGraph.STATUS_RUNNING = 2;\n\n    /**\n     * Removes all nodes from this graph\n     * @method clear\n     */\n\n    LGraph.prototype.clear = function() {\n        this.stop();\n        this.status = LGraph.STATUS_STOPPED;\n\n        this.last_node_id = 0;\n        this.last_link_id = 0;\n\n        this._version = -1; //used to detect changes\n\n        //safe clear\n        if (this._nodes) {\n            for (var i = 0; i < this._nodes.length; ++i) {\n                var node = this._nodes[i];\n                if (node.onRemoved) {\n                    node.onRemoved();\n                }\n            }\n        }\n\n        //nodes\n        this._nodes = [];\n        this._nodes_by_id = {};\n        this._nodes_in_order = []; //nodes sorted in execution order\n        this._nodes_executable = null; //nodes that contain onExecute sorted in execution order\n\n        //other scene stuff\n        this._groups = [];\n\n        //links\n        this.links = {}; //container with all the links\n\n        //iterations\n        this.iteration = 0;\n\n        //custom data\n        this.config = {};\n\t\tthis.vars = {};\n\t\tthis.extra = {}; //to store custom data\n\n        //timing\n        this.globaltime = 0;\n        this.runningtime = 0;\n        this.fixedtime = 0;\n        this.fixedtime_lapse = 0.01;\n        this.elapsed_time = 0.01;\n        this.last_update_time = 0;\n        this.starttime = 0;\n\n        this.catch_errors = true;\n\n        this.nodes_executing = [];\n        this.nodes_actioning = [];\n        this.nodes_executedAction = [];\n        \n        //subgraph_data\n        this.inputs = {};\n        this.outputs = {};\n\n        //notify canvas to redraw\n        this.change();\n\n        this.sendActionToCanvas(\"clear\");\n    };\n\n    /**\n     * Attach Canvas to this graph\n     * @method attachCanvas\n     * @param {GraphCanvas} graph_canvas\n     */\n\n    LGraph.prototype.attachCanvas = function(graphcanvas) {\n        if (graphcanvas.constructor != LGraphCanvas) {\n            throw \"attachCanvas expects a LGraphCanvas instance\";\n        }\n        if (graphcanvas.graph && graphcanvas.graph != this) {\n            graphcanvas.graph.detachCanvas(graphcanvas);\n        }\n\n        graphcanvas.graph = this;\n\n        if (!this.list_of_graphcanvas) {\n            this.list_of_graphcanvas = [];\n        }\n        this.list_of_graphcanvas.push(graphcanvas);\n    };\n\n    /**\n     * Detach Canvas from this graph\n     * @method detachCanvas\n     * @param {GraphCanvas} graph_canvas\n     */\n    LGraph.prototype.detachCanvas = function(graphcanvas) {\n        if (!this.list_of_graphcanvas) {\n            return;\n        }\n\n        var pos = this.list_of_graphcanvas.indexOf(graphcanvas);\n        if (pos == -1) {\n            return;\n        }\n        graphcanvas.graph = null;\n        this.list_of_graphcanvas.splice(pos, 1);\n    };\n\n    /**\n     * Starts running this graph every interval milliseconds.\n     * @method start\n     * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate\n     */\n\n    LGraph.prototype.start = function(interval) {\n        if (this.status == LGraph.STATUS_RUNNING) {\n            return;\n        }\n        this.status = LGraph.STATUS_RUNNING;\n\n        if (this.onPlayEvent) {\n            this.onPlayEvent();\n        }\n\n        this.sendEventToAllNodes(\"onStart\");\n\n        //launch\n        this.starttime = LiteGraph.getTime();\n        this.last_update_time = this.starttime;\n        interval = interval || 0;\n        var that = this;\n\n\t\t//execute once per frame\n        if ( interval == 0 && typeof window != \"undefined\" && window.requestAnimationFrame ) {\n            function on_frame() {\n                if (that.execution_timer_id != -1) {\n                    return;\n                }\n                window.requestAnimationFrame(on_frame);\n\t\t\t\tif(that.onBeforeStep)\n\t\t\t\t\tthat.onBeforeStep();\n                that.runStep(1, !that.catch_errors);\n\t\t\t\tif(that.onAfterStep)\n\t\t\t\t\tthat.onAfterStep();\n            }\n            this.execution_timer_id = -1;\n            on_frame();\n        } else { //execute every 'interval' ms\n            this.execution_timer_id = setInterval(function() {\n                //execute\n\t\t\t\tif(that.onBeforeStep)\n\t\t\t\t\tthat.onBeforeStep();\n                that.runStep(1, !that.catch_errors);\n\t\t\t\tif(that.onAfterStep)\n\t\t\t\t\tthat.onAfterStep();\n            }, interval);\n        }\n    };\n\n    /**\n     * Stops the execution loop of the graph\n     * @method stop execution\n     */\n\n    LGraph.prototype.stop = function() {\n        if (this.status == LGraph.STATUS_STOPPED) {\n            return;\n        }\n\n        this.status = LGraph.STATUS_STOPPED;\n\n        if (this.onStopEvent) {\n            this.onStopEvent();\n        }\n\n        if (this.execution_timer_id != null) {\n            if (this.execution_timer_id != -1) {\n                clearInterval(this.execution_timer_id);\n            }\n            this.execution_timer_id = null;\n        }\n\n        this.sendEventToAllNodes(\"onStop\");\n    };\n\n    /**\n     * Run N steps (cycles) of the graph\n     * @method runStep\n     * @param {number} num number of steps to run, default is 1\n     * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors \n     * @param {number} limit max number of nodes to execute (used to execute from start to a node)\n     */\n\n    LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) {\n        num = num || 1;\n\n        var start = LiteGraph.getTime();\n        this.globaltime = 0.001 * (start - this.starttime);\n\n        //not optimal: executes possible pending actions in node, problem is it is not optimized\n        //it is done here as if it was done in the later loop it wont be called in the node missed the onExecute\n        \n        //from now on it will iterate only on executable nodes which is faster\n        var nodes = this._nodes_executable\n            ? this._nodes_executable\n            : this._nodes;\n        if (!nodes) {\n            return;\n        }\n\n\t\tlimit = limit || nodes.length;\n\n        if (do_not_catch_errors) {\n            //iterations\n            for (var i = 0; i < num; i++) {\n                for (var j = 0; j < limit; ++j) {\n                    var node = nodes[j];\n                    if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length)\n                        node.executePendingActions();\n                    if (node.mode == LiteGraph.ALWAYS && node.onExecute) {\n                        //wrap node.onExecute();\n\t\t\t\t\t\tnode.doExecute();\n                    }\n                }\n\n                this.fixedtime += this.fixedtime_lapse;\n                if (this.onExecuteStep) {\n                    this.onExecuteStep();\n                }\n            }\n\n            if (this.onAfterExecute) {\n                this.onAfterExecute();\n            }\n        } else { //catch errors\n            try {\n                //iterations\n                for (var i = 0; i < num; i++) {\n                    for (var j = 0; j < limit; ++j) {\n                        var node = nodes[j];\n                        if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length)\n                            node.executePendingActions();\n                        if (node.mode == LiteGraph.ALWAYS && node.onExecute) {\n                            node.onExecute();\n                        }\n                    }\n\n                    this.fixedtime += this.fixedtime_lapse;\n                    if (this.onExecuteStep) {\n                        this.onExecuteStep();\n                    }\n                }\n\n                if (this.onAfterExecute) {\n                    this.onAfterExecute();\n                }\n                this.errors_in_execution = false;\n            } catch (err) {\n                this.errors_in_execution = true;\n                if (LiteGraph.throw_errors) {\n                    throw err;\n                }\n                if (LiteGraph.debug) {\n                    console.log(\"Error during execution: \" + err);\n                }\n                this.stop();\n            }\n        }\n\n        var now = LiteGraph.getTime();\n        var elapsed = now - start;\n        if (elapsed == 0) {\n            elapsed = 1;\n        }\n        this.execution_time = 0.001 * elapsed;\n        this.globaltime += 0.001 * elapsed;\n        this.iteration += 1;\n        this.elapsed_time = (now - this.last_update_time) * 0.001;\n        this.last_update_time = now;\n        this.nodes_executing = [];\n        this.nodes_actioning = [];\n        this.nodes_executedAction = [];\n    };\n\n    /**\n     * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than\n     * nodes with only inputs.\n     * @method updateExecutionOrder\n     */\n    LGraph.prototype.updateExecutionOrder = function() {\n        this._nodes_in_order = this.computeExecutionOrder(false);\n        this._nodes_executable = [];\n        for (var i = 0; i < this._nodes_in_order.length; ++i) {\n            if (this._nodes_in_order[i].onExecute) {\n                this._nodes_executable.push(this._nodes_in_order[i]);\n            }\n        }\n    };\n\n    //This is more internal, it computes the executable nodes in order and returns it\n    LGraph.prototype.computeExecutionOrder = function(\n        only_onExecute,\n        set_level\n    ) {\n        var L = [];\n        var S = [];\n        var M = {};\n        var visited_links = {}; //to avoid repeating links\n        var remaining_links = {}; //to a\n\n        //search for the nodes without inputs (starting nodes)\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            var node = this._nodes[i];\n            if (only_onExecute && !node.onExecute) {\n                continue;\n            }\n\n            M[node.id] = node; //add to pending nodes\n\n            var num = 0; //num of input connections\n            if (node.inputs) {\n                for (var j = 0, l2 = node.inputs.length; j < l2; j++) {\n                    if (node.inputs[j] && node.inputs[j].link != null) {\n                        num += 1;\n                    }\n                }\n            }\n\n            if (num == 0) {\n                //is a starting node\n                S.push(node);\n                if (set_level) {\n                    node._level = 1;\n                }\n            } //num of input links\n            else {\n                if (set_level) {\n                    node._level = 0;\n                }\n                remaining_links[node.id] = num;\n            }\n        }\n\n        while (true) {\n            if (S.length == 0) {\n                break;\n            }\n\n            //get an starting node\n            var node = S.shift();\n            L.push(node); //add to ordered list\n            delete M[node.id]; //remove from the pending nodes\n\n            if (!node.outputs) {\n                continue;\n            }\n\n            //for every output\n            for (var i = 0; i < node.outputs.length; i++) {\n                var output = node.outputs[i];\n                //not connected\n                if (\n                    output == null ||\n                    output.links == null ||\n                    output.links.length == 0\n                ) {\n                    continue;\n                }\n\n                //for every connection\n                for (var j = 0; j < output.links.length; j++) {\n                    var link_id = output.links[j];\n                    var link = this.links[link_id];\n                    if (!link) {\n                        continue;\n                    }\n\n                    //already visited link (ignore it)\n                    if (visited_links[link.id]) {\n                        continue;\n                    }\n\n                    var target_node = this.getNodeById(link.target_id);\n                    if (target_node == null) {\n                        visited_links[link.id] = true;\n                        continue;\n                    }\n\n                    if (\n                        set_level &&\n                        (!target_node._level ||\n                            target_node._level <= node._level)\n                    ) {\n                        target_node._level = node._level + 1;\n                    }\n\n                    visited_links[link.id] = true; //mark as visited\n                    remaining_links[target_node.id] -= 1; //reduce the number of links remaining\n                    if (remaining_links[target_node.id] == 0) {\n                        S.push(target_node);\n                    } //if no more links, then add to starters array\n                }\n            }\n        }\n\n        //the remaining ones (loops)\n        for (var i in M) {\n            L.push(M[i]);\n        }\n\n        if (L.length != this._nodes.length && LiteGraph.debug) {\n            console.warn(\"something went wrong, nodes missing\");\n        }\n\n        var l = L.length;\n\n        //save order number in the node\n        for (var i = 0; i < l; ++i) {\n            L[i].order = i;\n        }\n\n        //sort now by priority\n        L = L.sort(function(A, B) {\n            var Ap = A.constructor.priority || A.priority || 0;\n            var Bp = B.constructor.priority || B.priority || 0;\n            if (Ap == Bp) {\n                //if same priority, sort by order\n                return A.order - B.order;\n            }\n            return Ap - Bp; //sort by priority\n        });\n\n        //save order number in the node, again...\n        for (var i = 0; i < l; ++i) {\n            L[i].order = i;\n        }\n\n        return L;\n    };\n\n    /**\n     * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.\n     * It doesn't include the node itself\n     * @method getAncestors\n     * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution\n     */\n    LGraph.prototype.getAncestors = function(node) {\n        var ancestors = [];\n        var pending = [node];\n        var visited = {};\n\n        while (pending.length) {\n            var current = pending.shift();\n            if (!current.inputs) {\n                continue;\n            }\n            if (!visited[current.id] && current != node) {\n                visited[current.id] = true;\n                ancestors.push(current);\n            }\n\n            for (var i = 0; i < current.inputs.length; ++i) {\n                var input = current.getInputNode(i);\n                if (input && ancestors.indexOf(input) == -1) {\n                    pending.push(input);\n                }\n            }\n        }\n\n        ancestors.sort(function(a, b) {\n            return a.order - b.order;\n        });\n        return ancestors;\n    };\n\n    /**\n     * Positions every node in a more readable manner\n     * @method arrange\n     */\n    LGraph.prototype.arrange = function (margin, layout) {\n        margin = margin || 100;\n\n        const nodes = this.computeExecutionOrder(false, true);\n        const columns = [];\n        for (let i = 0; i < nodes.length; ++i) {\n            const node = nodes[i];\n            const col = node._level || 1;\n            if (!columns[col]) {\n                columns[col] = [];\n            }\n            columns[col].push(node);\n        }\n\n        let x = margin;\n\n        for (let i = 0; i < columns.length; ++i) {\n            const column = columns[i];\n            if (!column) {\n                continue;\n            }\n            let max_size = 100;\n            let y = margin + LiteGraph.NODE_TITLE_HEIGHT;\n            for (let j = 0; j < column.length; ++j) {\n                const node = column[j];\n                node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x;\n                node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y;\n                const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0;\n                if (node.size[max_size_index] > max_size) {\n                    max_size = node.size[max_size_index];\n                }\n                const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1;\n                y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT;\n            }\n            x += max_size + margin;\n        }\n\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * Returns the amount of time the graph has been running in milliseconds\n     * @method getTime\n     * @return {number} number of milliseconds the graph has been running\n     */\n    LGraph.prototype.getTime = function() {\n        return this.globaltime;\n    };\n\n    /**\n     * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant\n     * @method getFixedTime\n     * @return {number} number of milliseconds the graph has been running\n     */\n\n    LGraph.prototype.getFixedTime = function() {\n        return this.fixedtime;\n    };\n\n    /**\n     * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct\n     * if the nodes are using graphical actions\n     * @method getElapsedTime\n     * @return {number} number of milliseconds it took the last cycle\n     */\n\n    LGraph.prototype.getElapsedTime = function() {\n        return this.elapsed_time;\n    };\n\n    /**\n     * Sends an event to all the nodes, useful to trigger stuff\n     * @method sendEventToAllNodes\n     * @param {String} eventname the name of the event (function to be called)\n     * @param {Array} params parameters in array format\n     */\n    LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) {\n        mode = mode || LiteGraph.ALWAYS;\n\n        var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes;\n        if (!nodes) {\n            return;\n        }\n\n        for (var j = 0, l = nodes.length; j < l; ++j) {\n            var node = nodes[j];\n\n            if (\n                node.constructor === LiteGraph.Subgraph &&\n                eventname != \"onExecute\"\n            ) {\n                if (node.mode == mode) {\n                    node.sendEventToAllNodes(eventname, params, mode);\n                }\n                continue;\n            }\n\n            if (!node[eventname] || node.mode != mode) {\n                continue;\n            }\n            if (params === undefined) {\n                node[eventname]();\n            } else if (params && params.constructor === Array) {\n                node[eventname].apply(node, params);\n            } else {\n                node[eventname](params);\n            }\n        }\n    };\n\n    LGraph.prototype.sendActionToCanvas = function(action, params) {\n        if (!this.list_of_graphcanvas) {\n            return;\n        }\n\n        for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n            var c = this.list_of_graphcanvas[i];\n            if (c[action]) {\n                c[action].apply(c, params);\n            }\n        }\n    };\n\n    /**\n     * Adds a new node instance to this graph\n     * @method add\n     * @param {LGraphNode} node the instance of the node\n     */\n\n    LGraph.prototype.add = function(node, skip_compute_order) {\n        if (!node) {\n            return;\n        }\n\n        //groups\n        if (node.constructor === LGraphGroup) {\n            this._groups.push(node);\n            this.setDirtyCanvas(true);\n            this.change();\n            node.graph = this;\n            this._version++;\n            return;\n        }\n\n        //nodes\n        if (node.id != -1 && this._nodes_by_id[node.id] != null) {\n            console.warn(\n                \"LiteGraph: there is already a node with this ID, changing it\"\n            );\n            if (LiteGraph.use_uuids) {\n                node.id = LiteGraph.uuidv4();\n            }\n            else {\n                node.id = ++this.last_node_id;\n            }\n        }\n\n        if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) {\n            throw \"LiteGraph: max number of nodes in a graph reached\";\n        }\n\n        //give him an id\n        if (LiteGraph.use_uuids) {\n            if (node.id == null || node.id == -1)\n                node.id = LiteGraph.uuidv4();\n        }\n        else {\n            if (node.id == null || node.id == -1) {\n                node.id = ++this.last_node_id;\n            } else if (this.last_node_id < node.id) {\n                this.last_node_id = node.id;\n            }\n        }\n\n        node.graph = this;\n        this._version++;\n\n        this._nodes.push(node);\n        this._nodes_by_id[node.id] = node;\n\n        if (node.onAdded) {\n            node.onAdded(this);\n        }\n\n        if (this.config.align_to_grid) {\n            node.alignToGrid();\n        }\n\n        if (!skip_compute_order) {\n            this.updateExecutionOrder();\n        }\n\n        if (this.onNodeAdded) {\n            this.onNodeAdded(node);\n        }\n\n        this.setDirtyCanvas(true);\n        this.change();\n\n        return node; //to chain actions\n    };\n\n    /**\n     * Removes a node from the graph\n     * @method remove\n     * @param {LGraphNode} node the instance of the node\n     */\n\n    LGraph.prototype.remove = function(node) {\n        if (node.constructor === LiteGraph.LGraphGroup) {\n            var index = this._groups.indexOf(node);\n            if (index != -1) {\n                this._groups.splice(index, 1);\n            }\n            node.graph = null;\n            this._version++;\n            this.setDirtyCanvas(true, true);\n            this.change();\n            return;\n        }\n\n        if (this._nodes_by_id[node.id] == null) {\n            return;\n        } //not found\n\n        if (node.ignore_remove) {\n            return;\n        } //cannot be removed\n\n\t\tthis.beforeChange(); //sure? - almost sure is wrong\n\n        //disconnect inputs\n        if (node.inputs) {\n            for (var i = 0; i < node.inputs.length; i++) {\n                var slot = node.inputs[i];\n                if (slot.link != null) {\n                    node.disconnectInput(i);\n                }\n            }\n        }\n\n        //disconnect outputs\n        if (node.outputs) {\n            for (var i = 0; i < node.outputs.length; i++) {\n                var slot = node.outputs[i];\n                if (slot.links != null && slot.links.length) {\n                    node.disconnectOutput(i);\n                }\n            }\n        }\n\n        //node.id = -1; //why?\n\n        //callback\n        if (node.onRemoved) {\n            node.onRemoved();\n        }\n\n        node.graph = null;\n        this._version++;\n\n        //remove from canvas render\n        if (this.list_of_graphcanvas) {\n            for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n                var canvas = this.list_of_graphcanvas[i];\n                if (canvas.selected_nodes[node.id]) {\n                    delete canvas.selected_nodes[node.id];\n                }\n                if (canvas.node_dragged == node) {\n                    canvas.node_dragged = null;\n                }\n            }\n        }\n\n        //remove from containers\n        var pos = this._nodes.indexOf(node);\n        if (pos != -1) {\n            this._nodes.splice(pos, 1);\n        }\n        delete this._nodes_by_id[node.id];\n\n        if (this.onNodeRemoved) {\n            this.onNodeRemoved(node);\n        }\n\n\t\t//close panels\n\t\tthis.sendActionToCanvas(\"checkPanels\");\n\n        this.setDirtyCanvas(true, true);\n\t\tthis.afterChange(); //sure? - almost sure is wrong\n        this.change();\n\n        this.updateExecutionOrder();\n    };\n\n    /**\n     * Returns a node by its id.\n     * @method getNodeById\n     * @param {Number} id\n     */\n\n    LGraph.prototype.getNodeById = function(id) {\n        if (id == null) {\n            return null;\n        }\n        return this._nodes_by_id[id];\n    };\n\n    /**\n     * Returns a list of nodes that matches a class\n     * @method findNodesByClass\n     * @param {Class} classObject the class itself (not an string)\n     * @return {Array} a list with all the nodes of this type\n     */\n    LGraph.prototype.findNodesByClass = function(classObject, result) {\n        result = result || [];\n        result.length = 0;\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].constructor === classObject) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns a list of nodes that matches a type\n     * @method findNodesByType\n     * @param {String} type the name of the node type\n     * @return {Array} a list with all the nodes of this type\n     */\n    LGraph.prototype.findNodesByType = function(type, result) {\n        var type = type.toLowerCase();\n        result = result || [];\n        result.length = 0;\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].type.toLowerCase() == type) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns the first node that matches a name in its title\n     * @method findNodeByTitle\n     * @param {String} name the name of the node to search\n     * @return {Node} the node or null\n     */\n    LGraph.prototype.findNodeByTitle = function(title) {\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].title == title) {\n                return this._nodes[i];\n            }\n        }\n        return null;\n    };\n\n    /**\n     * Returns a list of nodes that matches a name\n     * @method findNodesByTitle\n     * @param {String} name the name of the node to search\n     * @return {Array} a list with all the nodes with this name\n     */\n    LGraph.prototype.findNodesByTitle = function(title) {\n        var result = [];\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].title == title) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns the top-most node in this position of the canvas\n     * @method getNodeOnPos\n     * @param {number} x the x coordinate in canvas space\n     * @param {number} y the y coordinate in canvas space\n     * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph\n     * @return {LGraphNode} the node at this position or null\n     */\n    LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) {\n        nodes_list = nodes_list || this._nodes;\n\t\tvar nRet = null;\n        for (var i = nodes_list.length - 1; i >= 0; i--) {\n            var n = nodes_list[i];\n            if (n.isPointInside(x, y, margin)) {\n                // check for lesser interest nodes (TODO check for overlapping, use the top)\n\t\t\t\t/*if (typeof n == \"LGraphGroup\"){\n\t\t\t\t\tnRet = n;\n\t\t\t\t}else{*/\n\t\t\t\t\treturn n;\n\t\t\t\t/*}*/\n            }\n        }\n        return nRet;\n    };\n\n    /**\n     * Returns the top-most group in that position\n     * @method getGroupOnPos\n     * @param {number} x the x coordinate in canvas space\n     * @param {number} y the y coordinate in canvas space\n     * @return {LGraphGroup} the group or null\n     */\n    LGraph.prototype.getGroupOnPos = function(x, y) {\n        for (var i = this._groups.length - 1; i >= 0; i--) {\n            var g = this._groups[i];\n            if (g.isPointInside(x, y, 2, true)) {\n                return g;\n            }\n        }\n        return null;\n    };\n\n    /**\n     * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution\n     * this replaces the ones using the old version with the new version\n     * @method checkNodeTypes\n     */\n    LGraph.prototype.checkNodeTypes = function() {\n        var changes = false;\n        for (var i = 0; i < this._nodes.length; i++) {\n            var node = this._nodes[i];\n            var ctor = LiteGraph.registered_node_types[node.type];\n            if (node.constructor == ctor) {\n                continue;\n            }\n            console.log(\"node being replaced by newer version: \" + node.type);\n            var newnode = LiteGraph.createNode(node.type);\n            changes = true;\n            this._nodes[i] = newnode;\n            newnode.configure(node.serialize());\n            newnode.graph = this;\n            this._nodes_by_id[newnode.id] = newnode;\n            if (node.inputs) {\n                newnode.inputs = node.inputs.concat();\n            }\n            if (node.outputs) {\n                newnode.outputs = node.outputs.concat();\n            }\n        }\n        this.updateExecutionOrder();\n    };\n\n    // ********** GLOBALS *****************\n\n    LGraph.prototype.onAction = function(action, param, options) {\n        this._input_nodes = this.findNodesByClass(\n            LiteGraph.GraphInput,\n            this._input_nodes\n        );\n        for (var i = 0; i < this._input_nodes.length; ++i) {\n            var node = this._input_nodes[i];\n            if (node.properties.name != action) {\n                continue;\n            }\n            //wrap node.onAction(action, param);\n            node.actionDo(action, param, options);\n            break;\n        }\n    };\n\n    LGraph.prototype.trigger = function(action, param) {\n        if (this.onTrigger) {\n            this.onTrigger(action, param);\n        }\n    };\n\n    /**\n     * Tell this graph it has a global graph input of this type\n     * @method addGlobalInput\n     * @param {String} name\n     * @param {String} type\n     * @param {*} value [optional]\n     */\n    LGraph.prototype.addInput = function(name, type, value) {\n        var input = this.inputs[name];\n        if (input) {\n            //already exist\n            return;\n        }\n\n\t\tthis.beforeChange();\n        this.inputs[name] = { name: name, type: type, value: value };\n        this._version++;\n\t\tthis.afterChange();\n\n        if (this.onInputAdded) {\n            this.onInputAdded(name, type);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Assign a data to the global graph input\n     * @method setGlobalInputData\n     * @param {String} name\n     * @param {*} data\n     */\n    LGraph.prototype.setInputData = function(name, data) {\n        var input = this.inputs[name];\n        if (!input) {\n            return;\n        }\n        input.value = data;\n    };\n\n    /**\n     * Returns the current value of a global graph input\n     * @method getInputData\n     * @param {String} name\n     * @return {*} the data\n     */\n    LGraph.prototype.getInputData = function(name) {\n        var input = this.inputs[name];\n        if (!input) {\n            return null;\n        }\n        return input.value;\n    };\n\n    /**\n     * Changes the name of a global graph input\n     * @method renameInput\n     * @param {String} old_name\n     * @param {String} new_name\n     */\n    LGraph.prototype.renameInput = function(old_name, name) {\n        if (name == old_name) {\n            return;\n        }\n\n        if (!this.inputs[old_name]) {\n            return false;\n        }\n\n        if (this.inputs[name]) {\n            console.error(\"there is already one input with that name\");\n            return false;\n        }\n\n        this.inputs[name] = this.inputs[old_name];\n        delete this.inputs[old_name];\n        this._version++;\n\n        if (this.onInputRenamed) {\n            this.onInputRenamed(old_name, name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Changes the type of a global graph input\n     * @method changeInputType\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.changeInputType = function(name, type) {\n        if (!this.inputs[name]) {\n            return false;\n        }\n\n        if (\n            this.inputs[name].type &&\n            String(this.inputs[name].type).toLowerCase() ==\n                String(type).toLowerCase()\n        ) {\n            return;\n        }\n\n        this.inputs[name].type = type;\n        this._version++;\n        if (this.onInputTypeChanged) {\n            this.onInputTypeChanged(name, type);\n        }\n    };\n\n    /**\n     * Removes a global graph input\n     * @method removeInput\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.removeInput = function(name) {\n        if (!this.inputs[name]) {\n            return false;\n        }\n\n        delete this.inputs[name];\n        this._version++;\n\n        if (this.onInputRemoved) {\n            this.onInputRemoved(name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n        return true;\n    };\n\n    /**\n     * Creates a global graph output\n     * @method addOutput\n     * @param {String} name\n     * @param {String} type\n     * @param {*} value\n     */\n    LGraph.prototype.addOutput = function(name, type, value) {\n        this.outputs[name] = { name: name, type: type, value: value };\n        this._version++;\n\n        if (this.onOutputAdded) {\n            this.onOutputAdded(name, type);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Assign a data to the global output\n     * @method setOutputData\n     * @param {String} name\n     * @param {String} value\n     */\n    LGraph.prototype.setOutputData = function(name, value) {\n        var output = this.outputs[name];\n        if (!output) {\n            return;\n        }\n        output.value = value;\n    };\n\n    /**\n     * Returns the current value of a global graph output\n     * @method getOutputData\n     * @param {String} name\n     * @return {*} the data\n     */\n    LGraph.prototype.getOutputData = function(name) {\n        var output = this.outputs[name];\n        if (!output) {\n            return null;\n        }\n        return output.value;\n    };\n\n    /**\n     * Renames a global graph output\n     * @method renameOutput\n     * @param {String} old_name\n     * @param {String} new_name\n     */\n    LGraph.prototype.renameOutput = function(old_name, name) {\n        if (!this.outputs[old_name]) {\n            return false;\n        }\n\n        if (this.outputs[name]) {\n            console.error(\"there is already one output with that name\");\n            return false;\n        }\n\n        this.outputs[name] = this.outputs[old_name];\n        delete this.outputs[old_name];\n        this._version++;\n\n        if (this.onOutputRenamed) {\n            this.onOutputRenamed(old_name, name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Changes the type of a global graph output\n     * @method changeOutputType\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.changeOutputType = function(name, type) {\n        if (!this.outputs[name]) {\n            return false;\n        }\n\n        if (\n            this.outputs[name].type &&\n            String(this.outputs[name].type).toLowerCase() ==\n                String(type).toLowerCase()\n        ) {\n            return;\n        }\n\n        this.outputs[name].type = type;\n        this._version++;\n        if (this.onOutputTypeChanged) {\n            this.onOutputTypeChanged(name, type);\n        }\n    };\n\n    /**\n     * Removes a global graph output\n     * @method removeOutput\n     * @param {String} name\n     */\n    LGraph.prototype.removeOutput = function(name) {\n        if (!this.outputs[name]) {\n            return false;\n        }\n        delete this.outputs[name];\n        this._version++;\n\n        if (this.onOutputRemoved) {\n            this.onOutputRemoved(name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n        return true;\n    };\n\n    LGraph.prototype.triggerInput = function(name, value) {\n        var nodes = this.findNodesByTitle(name);\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].onTrigger(value);\n        }\n    };\n\n    LGraph.prototype.setCallback = function(name, func) {\n        var nodes = this.findNodesByTitle(name);\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].setTrigger(func);\n        }\n    };\n\n\t//used for undo, called before any change is made to the graph\n    LGraph.prototype.beforeChange = function(info) {\n        if (this.onBeforeChange) {\n            this.onBeforeChange(this,info);\n        }\n        this.sendActionToCanvas(\"onBeforeChange\", this);\n    };\n\n\t//used to resend actions, called after any change is made to the graph\n    LGraph.prototype.afterChange = function(info) {\n        if (this.onAfterChange) {\n            this.onAfterChange(this,info);\n        }\n        this.sendActionToCanvas(\"onAfterChange\", this);\n    };\n\n    LGraph.prototype.connectionChange = function(node, link_info) {\n        this.updateExecutionOrder();\n        if (this.onConnectionChange) {\n            this.onConnectionChange(node);\n        }\n        this._version++;\n        this.sendActionToCanvas(\"onConnectionChange\");\n    };\n\n    /**\n     * returns if the graph is in live mode\n     * @method isLive\n     */\n\n    LGraph.prototype.isLive = function() {\n        if (!this.list_of_graphcanvas) {\n            return false;\n        }\n\n        for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n            var c = this.list_of_graphcanvas[i];\n            if (c.live_mode) {\n                return true;\n            }\n        }\n        return false;\n    };\n\n    /**\n     * clears the triggered slot animation in all links (stop visual animation)\n     * @method clearTriggeredSlots\n     */\n    LGraph.prototype.clearTriggeredSlots = function() {\n        for (var i in this.links) {\n            var link_info = this.links[i];\n            if (!link_info) {\n                continue;\n            }\n            if (link_info._last_time) {\n                link_info._last_time = 0;\n            }\n        }\n    };\n\n    /* Called when something visually changed (not the graph!) */\n    LGraph.prototype.change = function() {\n        if (LiteGraph.debug) {\n            console.log(\"Graph changed\");\n        }\n        this.sendActionToCanvas(\"setDirty\", [true, true]);\n        if (this.on_change) {\n            this.on_change(this);\n        }\n    };\n\n    LGraph.prototype.setDirtyCanvas = function(fg, bg) {\n        this.sendActionToCanvas(\"setDirty\", [fg, bg]);\n    };\n\n    /**\n     * Destroys a link\n     * @method removeLink\n     * @param {Number} link_id\n     */\n    LGraph.prototype.removeLink = function(link_id) {\n        var link = this.links[link_id];\n        if (!link) {\n            return;\n        }\n        var node = this.getNodeById(link.target_id);\n        if (node) {\n            node.disconnectInput(link.target_slot);\n        }\n    };\n\n    //save and recover app state ***************************************\n    /**\n     * Creates a Object containing all the info about this graph, it can be serialized\n     * @method serialize\n     * @return {Object} value of the node\n     */\n    LGraph.prototype.serialize = function() {\n        var nodes_info = [];\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            nodes_info.push(this._nodes[i].serialize());\n        }\n\n        //pack link info into a non-verbose format\n        var links = [];\n        for (var i in this.links) {\n            //links is an OBJECT\n            var link = this.links[i];\n            if (!link.serialize) {\n                //weird bug I havent solved yet\n                console.warn(\n                    \"weird LLink bug, link info is not a LLink but a regular object\"\n                );\n                var link2 = new LLink();\n                for (var j in link) { \n                    link2[j] = link[j];\n                }\n                this.links[i] = link2;\n                link = link2;\n            }\n\n            links.push(link.serialize());\n        }\n\n        var groups_info = [];\n        for (var i = 0; i < this._groups.length; ++i) {\n            groups_info.push(this._groups[i].serialize());\n        }\n\n        var data = {\n            last_node_id: this.last_node_id,\n            last_link_id: this.last_link_id,\n            nodes: nodes_info,\n            links: links,\n            groups: groups_info,\n            config: this.config,\n\t\t\textra: this.extra,\n            version: LiteGraph.VERSION\n        };\n\n\t\tif(this.onSerialize)\n\t\t\tthis.onSerialize(data);\n\n        return data;\n    };\n\n    /**\n     * Configure a graph from a JSON string\n     * @method configure\n     * @param {String} str configure a graph from a JSON string\n     * @param {Boolean} returns if there was any error parsing\n     */\n    LGraph.prototype.configure = function(data, keep_old) {\n        if (!data) {\n            return;\n        }\n\n        if (!keep_old) {\n            this.clear();\n        }\n\n        var nodes = data.nodes;\n\n        //decode links info (they are very verbose)\n        if (data.links && data.links.constructor === Array) {\n            var links = [];\n            for (var i = 0; i < data.links.length; ++i) {\n                var link_data = data.links[i];\n\t\t\t\tif(!link_data) //weird bug\n\t\t\t\t{\n\t\t\t\t\tconsole.warn(\"serialized graph link data contains errors, skipping.\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n                var link = new LLink();\n                link.configure(link_data);\n                links[link.id] = link;\n            }\n            data.links = links;\n        }\n\n        //copy all stored fields\n        for (var i in data) {\n\t\t\tif(i == \"nodes\" || i == \"groups\" ) //links must be accepted\n\t\t\t\tcontinue;\n            this[i] = data[i];\n        }\n\n        var error = false;\n\n        //create nodes\n        this._nodes = [];\n        if (nodes) {\n            for (var i = 0, l = nodes.length; i < l; ++i) {\n                var n_info = nodes[i]; //stored info\n                var node = LiteGraph.createNode(n_info.type, n_info.title);\n                if (!node) {\n                    if (LiteGraph.debug) {\n                        console.log(\n                            \"Node not found or has errors: \" + n_info.type\n                        );\n                    }\n\n                    //in case of error we create a replacement node to avoid losing info\n                    node = new LGraphNode();\n                    node.last_serialization = n_info;\n                    node.has_errors = true;\n                    error = true;\n                    //continue;\n                }\n\n                node.id = n_info.id; //id it or it will create a new id\n                this.add(node, true); //add before configure, otherwise configure cannot create links\n            }\n\n            //configure nodes afterwards so they can reach each other\n            for (var i = 0, l = nodes.length; i < l; ++i) {\n                var n_info = nodes[i];\n                var node = this.getNodeById(n_info.id);\n                if (node) {\n                    node.configure(n_info);\n                }\n            }\n        }\n\n        //groups\n        this._groups.length = 0;\n        if (data.groups) {\n            for (var i = 0; i < data.groups.length; ++i) {\n                var group = new LiteGraph.LGraphGroup();\n                group.configure(data.groups[i]);\n                this.add(group);\n            }\n        }\n\n        this.updateExecutionOrder();\n\n\t\tthis.extra = data.extra || {};\n\n\t\tif(this.onConfigure)\n\t\t\tthis.onConfigure(data);\n\n        this._version++;\n        this.setDirtyCanvas(true, true);\n        return error;\n    };\n\n    LGraph.prototype.load = function(url, callback) {\n        var that = this;\n\n\t\t//from file\n\t\tif(url.constructor === File || url.constructor === Blob)\n\t\t{\n\t\t\tvar reader = new FileReader();\n\t\t\treader.addEventListener('load', function(event) {\n\t\t\t\tvar data = JSON.parse(event.target.result);\n\t\t\t\tthat.configure(data);\n\t\t\t\tif(callback)\n\t\t\t\t\tcallback();\n\t\t\t});\n\t\t\t\n\t\t\treader.readAsText(url);\n\t\t\treturn;\n\t\t}\n\n\t\t//is a string, then an URL\n        var req = new XMLHttpRequest();\n        req.open(\"GET\", url, true);\n        req.send(null);\n        req.onload = function(oEvent) {\n            if (req.status !== 200) {\n                console.error(\"Error loading graph:\", req.status, req.response);\n                return;\n            }\n            var data = JSON.parse( req.response );\n            that.configure(data);\n\t\t\tif(callback)\n\t\t\t\tcallback();\n        };\n        req.onerror = function(err) {\n            console.error(\"Error loading graph:\", err);\n        };\n    };\n\n    LGraph.prototype.onNodeTrace = function(node, msg, color) {\n        //TODO\n    };\n\n    //this is the class in charge of storing link information\n    function LLink(id, type, origin_id, origin_slot, target_id, target_slot) {\n        this.id = id;\n        this.type = type;\n        this.origin_id = origin_id;\n        this.origin_slot = origin_slot;\n        this.target_id = target_id;\n        this.target_slot = target_slot;\n\n        this._data = null;\n        this._pos = new Float32Array(2); //center\n    }\n\n    LLink.prototype.configure = function(o) {\n        if (o.constructor === Array) {\n            this.id = o[0];\n            this.origin_id = o[1];\n            this.origin_slot = o[2];\n            this.target_id = o[3];\n            this.target_slot = o[4];\n            this.type = o[5];\n        } else {\n            this.id = o.id;\n            this.type = o.type;\n            this.origin_id = o.origin_id;\n            this.origin_slot = o.origin_slot;\n            this.target_id = o.target_id;\n            this.target_slot = o.target_slot;\n        }\n    };\n\n    LLink.prototype.serialize = function() {\n        return [\n            this.id,\n            this.origin_id,\n            this.origin_slot,\n            this.target_id,\n            this.target_slot,\n            this.type\n        ];\n    };\n\n    LiteGraph.LLink = LLink;\n\n    // *************************************************************\n    //   Node CLASS                                          *******\n    // *************************************************************\n\n    /*\n\ttitle: string\n\tpos: [x,y]\n\tsize: [x,y]\n\n\tinput|output: every connection\n\t\t+  { name:string, type:string, pos: [x,y]=Optional, direction: \"input\"|\"output\", links: Array });\n\n\tgeneral properties:\n\t\t+ clip_area: if you render outside the node, it will be clipped\n\t\t+ unsafe_execution: not allowed for safe execution\n\t\t+ skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected\n\t\t+ resizable: if set to false it wont be resizable with the mouse\n\t\t+ horizontal: slots are distributed horizontally\n\t\t+ widgets_start_y: widgets start at y distance from the top of the node\n\t\n\tflags object:\n\t\t+ collapsed: if it is collapsed\n\n\tsupported callbacks:\n\t\t+ onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading)\n\t\t+ onRemoved: when removed from graph\n\t\t+ onStart:\twhen the graph starts playing\n\t\t+ onStop:\twhen the graph stops playing\n\t\t+ onDrawForeground: render the inside widgets inside the node\n\t\t+ onDrawBackground: render the background area inside the node (only in edit mode)\n\t\t+ onMouseDown\n\t\t+ onMouseMove\n\t\t+ onMouseUp\n\t\t+ onMouseEnter\n\t\t+ onMouseLeave\n\t\t+ onExecute: execute the node\n\t\t+ onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour)\n\t\t+ onGetInputs: returns an array of possible inputs\n\t\t+ onGetOutputs: returns an array of possible outputs\n\t\t+ onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h])\n\t\t+ onDblClick: double clicked in the node\n\t\t+ onInputDblClick: input slot double clicked (can be used to automatically create a node connected)\n\t\t+ onOutputDblClick: output slot double clicked (can be used to automatically create a node connected)\n\t\t+ onConfigure: called after the node has been configured\n\t\t+ onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data)\n\t\t+ onSelected\n\t\t+ onDeselected\n\t\t+ onDropItem : DOM item dropped over the node\n\t\t+ onDropFile : file dropped over the node\n\t\t+ onConnectInput : if returns false the incoming connection will be canceled\n\t\t+ onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info )\n\t\t+ onAction: action slot triggered\n\t\t+ getExtraMenuOptions: to add option to context menu\n*/\n\n    /**\n     * Base Class for all the node type classes\n     * @class LGraphNode\n     * @param {String} name a name for the node\n     */\n\n    function LGraphNode(title) {\n        this._ctor(title);\n    }\n\n    global.LGraphNode = LiteGraph.LGraphNode = LGraphNode;\n\n    LGraphNode.prototype._ctor = function(title) {\n        this.title = title || \"Unnamed\";\n        this.size = [LiteGraph.NODE_WIDTH, 60];\n        this.graph = null;\n\n        this._pos = new Float32Array(10, 10);\n\n        Object.defineProperty(this, \"pos\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._pos[0] = v[0];\n                this._pos[1] = v[1];\n            },\n            get: function() {\n                return this._pos;\n            },\n            enumerable: true\n        });\n\n        if (LiteGraph.use_uuids) {\n            this.id = LiteGraph.uuidv4();\n        }\n        else {\n            this.id = -1; //not know till not added\n        }\n        this.type = null;\n\n        //inputs available: array of inputs\n        this.inputs = [];\n        this.outputs = [];\n        this.connections = [];\n\n        //local data\n        this.properties = {}; //for the values\n        this.properties_info = []; //for the info\n\n        this.flags = {};\n    };\n\n    /**\n     * configure a node from an object containing the serialized info\n     * @method configure\n     */\n    LGraphNode.prototype.configure = function(info) {\n        if (this.graph) {\n            this.graph._version++;\n        }\n        for (var j in info) {\n            if (j == \"properties\") {\n                //i don't want to clone properties, I want to reuse the old container\n                for (var k in info.properties) {\n                    this.properties[k] = info.properties[k];\n                    if (this.onPropertyChanged) {\n                        this.onPropertyChanged( k, info.properties[k] );\n                    }\n                }\n                continue;\n            }\n\n            if (info[j] == null) {\n                continue;\n            } else if (typeof info[j] == \"object\") {\n                //object\n                if (this[j] && this[j].configure) {\n                    this[j].configure(info[j]);\n                } else {\n                    this[j] = LiteGraph.cloneObject(info[j], this[j]);\n                }\n            } //value\n            else {\n                this[j] = info[j];\n            }\n        }\n\n        if (!info.title) {\n            this.title = this.constructor.title;\n        }\n\n\t\tif (this.inputs) {\n\t\t\tfor (var i = 0; i < this.inputs.length; ++i) {\n\t\t\t\tvar input = this.inputs[i];\n\t\t\t\tvar link_info = this.graph ? this.graph.links[input.link] : null;\n\t\t\t\tif (this.onConnectionsChange)\n\t\t\t\t\tthis.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated\n\n\t\t\t\tif( this.onInputAdded )\n\t\t\t\t\tthis.onInputAdded(input);\n\n\t\t\t}\n\t\t}\n\n\t\tif (this.outputs) {\n\t\t\tfor (var i = 0; i < this.outputs.length; ++i) {\n\t\t\t\tvar output = this.outputs[i];\n\t\t\t\tif (!output.links) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfor (var j = 0; j < output.links.length; ++j) {\n\t\t\t\t\tvar link_info = this.graph \t? this.graph.links[output.links[j]] : null;\n\t\t\t\t\tif (this.onConnectionsChange)\n\t\t\t\t\t\tthis.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated\n\t\t\t\t}\n\n\t\t\t\tif( this.onOutputAdded )\n\t\t\t\t\tthis.onOutputAdded(output);\n\t\t\t}\n        }\n\n\t\tif( this.widgets )\n\t\t{\n\t\t\tfor (var i = 0; i < this.widgets.length; ++i)\n\t\t\t{\n\t\t\t\tvar w = this.widgets[i];\n\t\t\t\tif(!w)\n\t\t\t\t\tcontinue;\n\t\t\t\tif(w.options && w.options.property && (this.properties[ w.options.property ] != undefined))\n\t\t\t\t\tw.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) );\n\t\t\t}\n\t\t\tif (info.widgets_values) {\n\t\t\t\tfor (var i = 0; i < info.widgets_values.length; ++i) {\n\t\t\t\t\tif (this.widgets[i]) {\n\t\t\t\t\t\tthis.widgets[i].value = info.widgets_values[i];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n        if (this.onConfigure) {\n            this.onConfigure(info);\n        }\n    };\n\n    /**\n     * serialize the content\n     * @method serialize\n     */\n\n    LGraphNode.prototype.serialize = function() {\n        //create serialization object\n        var o = {\n            id: this.id,\n            type: this.type,\n            pos: this.pos,\n            size: this.size,\n            flags: LiteGraph.cloneObject(this.flags),\n\t\t\torder: this.order,\n            mode: this.mode\n        };\n\n        //special case for when there were errors\n        if (this.constructor === LGraphNode && this.last_serialization) {\n            return this.last_serialization;\n        }\n\n        if (this.inputs) {\n            o.inputs = this.inputs;\n        }\n\n        if (this.outputs) {\n            //clear outputs last data (because data in connections is never serialized but stored inside the outputs info)\n            for (var i = 0; i < this.outputs.length; i++) {\n                delete this.outputs[i]._data;\n            }\n            o.outputs = this.outputs;\n        }\n\n        if (this.title && this.title != this.constructor.title) {\n            o.title = this.title;\n        }\n\n        if (this.properties) {\n            o.properties = LiteGraph.cloneObject(this.properties);\n        }\n\n        if (this.widgets && this.serialize_widgets) {\n            o.widgets_values = [];\n            for (var i = 0; i < this.widgets.length; ++i) {\n\t\t\t\tif(this.widgets[i])\n\t                o.widgets_values[i] = this.widgets[i].value;\n\t\t\t\telse\n\t\t\t\t\to.widgets_values[i] = null;\n            }\n        }\n\n        if (!o.type) {\n            o.type = this.constructor.type;\n        }\n\n        if (this.color) {\n            o.color = this.color;\n        }\n        if (this.bgcolor) {\n            o.bgcolor = this.bgcolor;\n        }\n        if (this.boxcolor) {\n            o.boxcolor = this.boxcolor;\n        }\n        if (this.shape) {\n            o.shape = this.shape;\n        }\n\n        if (this.onSerialize) {\n            if (this.onSerialize(o)) {\n                console.warn(\n                    \"node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter\"\n                );\n            }\n        }\n\n        return o;\n    };\n\n    /* Creates a clone of this node */\n    LGraphNode.prototype.clone = function() {\n        var node = LiteGraph.createNode(this.type);\n        if (!node) {\n            return null;\n        }\n\n        //we clone it because serialize returns shared containers\n        var data = LiteGraph.cloneObject(this.serialize());\n\n        //remove links\n        if (data.inputs) {\n            for (var i = 0; i < data.inputs.length; ++i) {\n                data.inputs[i].link = null;\n            }\n        }\n\n        if (data.outputs) {\n            for (var i = 0; i < data.outputs.length; ++i) {\n                if (data.outputs[i].links) {\n                    data.outputs[i].links.length = 0;\n                }\n            }\n        }\n\n        delete data[\"id\"];\n\n        if (LiteGraph.use_uuids) {\n            data[\"id\"] = LiteGraph.uuidv4()\n        }\n\n        //remove links\n        node.configure(data);\n\n        return node;\n    };\n\n    /**\n     * serialize and stringify\n     * @method toString\n     */\n\n    LGraphNode.prototype.toString = function() {\n        return JSON.stringify(this.serialize());\n    };\n    //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph\n\n    /**\n     * get the title string\n     * @method getTitle\n     */\n\n    LGraphNode.prototype.getTitle = function() {\n        return this.title || this.constructor.title;\n    };\n\n    /**\n     * sets the value of a property\n     * @method setProperty\n     * @param {String} name\n     * @param {*} value\n     */\n    LGraphNode.prototype.setProperty = function(name, value) {\n        if (!this.properties) {\n            this.properties = {};\n        }\n\t\tif( value === this.properties[name] )\n\t\t\treturn;\n\t\tvar prev_value = this.properties[name];\n        this.properties[name] = value;\n        if (this.onPropertyChanged) {\n            if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change\n\t\t\t\tthis.properties[name] = prev_value;\n        }\n\t\tif(this.widgets) //widgets could be linked to properties\n\t\t\tfor(var i = 0; i < this.widgets.length; ++i)\n\t\t\t{\n\t\t\t\tvar w = this.widgets[i];\n\t\t\t\tif(!w)\n\t\t\t\t\tcontinue;\n\t\t\t\tif(w.options.property == name)\n\t\t\t\t{\n\t\t\t\t\tw.value = value;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n    };\n\n    // Execution *************************\n    /**\n     * sets the output data\n     * @method setOutputData\n     * @param {number} slot\n     * @param {*} data\n     */\n    LGraphNode.prototype.setOutputData = function(slot, data) {\n        if (!this.outputs) {\n            return;\n        }\n\n        //this maybe slow and a niche case\n        //if(slot && slot.constructor === String)\n        //\tslot = this.findOutputSlot(slot);\n\n        if (slot == -1 || slot >= this.outputs.length) {\n            return;\n        }\n\n        var output_info = this.outputs[slot];\n        if (!output_info) {\n            return;\n        }\n\n        //store data in the output itself in case we want to debug\n        output_info._data = data;\n\n        //if there are connections, pass the data to the connections\n        if (this.outputs[slot].links) {\n            for (var i = 0; i < this.outputs[slot].links.length; i++) {\n                var link_id = this.outputs[slot].links[i];\n\t\t\t\tvar link = this.graph.links[link_id];\n\t\t\t\tif(link)\n\t\t\t\t\tlink.data = data;\n            }\n        }\n    };\n\n    /**\n     * sets the output data type, useful when you want to be able to overwrite the data type\n     * @method setOutputDataType\n     * @param {number} slot\n     * @param {String} datatype\n     */\n    LGraphNode.prototype.setOutputDataType = function(slot, type) {\n        if (!this.outputs) {\n            return;\n        }\n        if (slot == -1 || slot >= this.outputs.length) {\n            return;\n        }\n        var output_info = this.outputs[slot];\n        if (!output_info) {\n            return;\n        }\n        //store data in the output itself in case we want to debug\n        output_info.type = type;\n\n        //if there are connections, pass the data to the connections\n        if (this.outputs[slot].links) {\n            for (var i = 0; i < this.outputs[slot].links.length; i++) {\n                var link_id = this.outputs[slot].links[i];\n                this.graph.links[link_id].type = type;\n            }\n        }\n    };\n\n    /**\n     * Retrieves the input data (data traveling through the connection) from one slot\n     * @method getInputData\n     * @param {number} slot\n     * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link\n     * @return {*} data or if it is not connected returns undefined\n     */\n    LGraphNode.prototype.getInputData = function(slot, force_update) {\n        if (!this.inputs) {\n            return;\n        } //undefined;\n\n        if (slot >= this.inputs.length || this.inputs[slot].link == null) {\n            return;\n        }\n\n        var link_id = this.inputs[slot].link;\n        var link = this.graph.links[link_id];\n        if (!link) {\n            //bug: weird case but it happens sometimes\n            return null;\n        }\n\n        if (!force_update) {\n            return link.data;\n        }\n\n        //special case: used to extract data from the incoming connection before the graph has been executed\n        var node = this.graph.getNodeById(link.origin_id);\n        if (!node) {\n            return link.data;\n        }\n\n        if (node.updateOutputData) {\n            node.updateOutputData(link.origin_slot);\n        } else if (node.onExecute) {\n            node.onExecute();\n        }\n\n        return link.data;\n    };\n\n    /**\n     * Retrieves the input data type (in case this supports multiple input types)\n     * @method getInputDataType\n     * @param {number} slot\n     * @return {String} datatype in string format\n     */\n    LGraphNode.prototype.getInputDataType = function(slot) {\n        if (!this.inputs) {\n            return null;\n        } //undefined;\n\n        if (slot >= this.inputs.length || this.inputs[slot].link == null) {\n            return null;\n        }\n        var link_id = this.inputs[slot].link;\n        var link = this.graph.links[link_id];\n        if (!link) {\n            //bug: weird case but it happens sometimes\n            return null;\n        }\n        var node = this.graph.getNodeById(link.origin_id);\n        if (!node) {\n            return link.type;\n        }\n        var output_info = node.outputs[link.origin_slot];\n        if (output_info) {\n            return output_info.type;\n        }\n        return null;\n    };\n\n    /**\n     * Retrieves the input data from one slot using its name instead of slot number\n     * @method getInputDataByName\n     * @param {String} slot_name\n     * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link\n     * @return {*} data or if it is not connected returns null\n     */\n    LGraphNode.prototype.getInputDataByName = function(\n        slot_name,\n        force_update\n    ) {\n        var slot = this.findInputSlot(slot_name);\n        if (slot == -1) {\n            return null;\n        }\n        return this.getInputData(slot, force_update);\n    };\n\n    /**\n     * tells you if there is a connection in one input slot\n     * @method isInputConnected\n     * @param {number} slot\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isInputConnected = function(slot) {\n        if (!this.inputs) {\n            return false;\n        }\n        return slot < this.inputs.length && this.inputs[slot].link != null;\n    };\n\n    /**\n     * tells you info about an input connection (which node, type, etc)\n     * @method getInputInfo\n     * @param {number} slot\n     * @return {Object} object or null { link: id, name: string, type: string or 0 }\n     */\n    LGraphNode.prototype.getInputInfo = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot < this.inputs.length) {\n            return this.inputs[slot];\n        }\n        return null;\n    };\n\n    /**\n     * Returns the link info in the connection of an input slot\n     * @method getInputLink\n     * @param {number} slot\n     * @return {LLink} object or null\n     */\n    LGraphNode.prototype.getInputLink = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot < this.inputs.length) {\n            var slot_info = this.inputs[slot];\n\t\t\treturn this.graph.links[ slot_info.link ];\n        }\n        return null;\n    };\n\n    /**\n     * returns the node connected in the input slot\n     * @method getInputNode\n     * @param {number} slot\n     * @return {LGraphNode} node or null\n     */\n    LGraphNode.prototype.getInputNode = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot >= this.inputs.length) {\n            return null;\n        }\n        var input = this.inputs[slot];\n        if (!input || input.link === null) {\n            return null;\n        }\n        var link_info = this.graph.links[input.link];\n        if (!link_info) {\n            return null;\n        }\n        return this.graph.getNodeById(link_info.origin_id);\n    };\n\n    /**\n     * returns the value of an input with this name, otherwise checks if there is a property with that name\n     * @method getInputOrProperty\n     * @param {string} name\n     * @return {*} value\n     */\n    LGraphNode.prototype.getInputOrProperty = function(name) {\n        if (!this.inputs || !this.inputs.length) {\n            return this.properties ? this.properties[name] : null;\n        }\n\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            var input_info = this.inputs[i];\n            if (name == input_info.name && input_info.link != null) {\n                var link = this.graph.links[input_info.link];\n                if (link) {\n                    return link.data;\n                }\n            }\n        }\n        return this.properties[name];\n    };\n\n    /**\n     * tells you the last output data that went in that slot\n     * @method getOutputData\n     * @param {number} slot\n     * @return {Object}  object or null\n     */\n    LGraphNode.prototype.getOutputData = function(slot) {\n        if (!this.outputs) {\n            return null;\n        }\n        if (slot >= this.outputs.length) {\n            return null;\n        }\n\n        var info = this.outputs[slot];\n        return info._data;\n    };\n\n    /**\n     * tells you info about an output connection (which node, type, etc)\n     * @method getOutputInfo\n     * @param {number} slot\n     * @return {Object}  object or null { name: string, type: string, links: [ ids of links in number ] }\n     */\n    LGraphNode.prototype.getOutputInfo = function(slot) {\n        if (!this.outputs) {\n            return null;\n        }\n        if (slot < this.outputs.length) {\n            return this.outputs[slot];\n        }\n        return null;\n    };\n\n    /**\n     * tells you if there is a connection in one output slot\n     * @method isOutputConnected\n     * @param {number} slot\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isOutputConnected = function(slot) {\n        if (!this.outputs) {\n            return false;\n        }\n        return (\n            slot < this.outputs.length &&\n            this.outputs[slot].links &&\n            this.outputs[slot].links.length\n        );\n    };\n\n    /**\n     * tells you if there is any connection in the output slots\n     * @method isAnyOutputConnected\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isAnyOutputConnected = function() {\n        if (!this.outputs) {\n            return false;\n        }\n        for (var i = 0; i < this.outputs.length; ++i) {\n            if (this.outputs[i].links && this.outputs[i].links.length) {\n                return true;\n            }\n        }\n        return false;\n    };\n\n    /**\n     * retrieves all the nodes connected to this output slot\n     * @method getOutputNodes\n     * @param {number} slot\n     * @return {array}\n     */\n    LGraphNode.prototype.getOutputNodes = function(slot) {\n        if (!this.outputs || this.outputs.length == 0) {\n            return null;\n        }\n\n        if (slot >= this.outputs.length) {\n            return null;\n        }\n\n        var output = this.outputs[slot];\n        if (!output.links || output.links.length == 0) {\n            return null;\n        }\n\n        var r = [];\n        for (var i = 0; i < output.links.length; i++) {\n            var link_id = output.links[i];\n            var link = this.graph.links[link_id];\n            if (link) {\n                var target_node = this.graph.getNodeById(link.target_id);\n                if (target_node) {\n                    r.push(target_node);\n                }\n            }\n        }\n        return r;\n    };\n\n    LGraphNode.prototype.addOnTriggerInput = function(){\n        var trigS = this.findInputSlot(\"onTrigger\");\n        if (trigS == -1){ //!trigS || \n            var input = this.addInput(\"onTrigger\", LiteGraph.EVENT, {optional: true, nameLocked: true});\n            return this.findInputSlot(\"onTrigger\");\n        }\n        return trigS;\n    }\n    \n    LGraphNode.prototype.addOnExecutedOutput = function(){\n        var trigS = this.findOutputSlot(\"onExecuted\");\n        if (trigS == -1){ //!trigS || \n            var output = this.addOutput(\"onExecuted\", LiteGraph.ACTION, {optional: true, nameLocked: true});\n            return this.findOutputSlot(\"onExecuted\");\n        }\n        return trigS;\n    }\n    \n    LGraphNode.prototype.onAfterExecuteNode = function(param, options){\n        var trigS = this.findOutputSlot(\"onExecuted\");\n        if (trigS != -1){\n            \n            //console.debug(this.id+\":\"+this.order+\" triggering slot onAfterExecute\");\n            //console.debug(param);\n            //console.debug(options);\n            this.triggerSlot(trigS, param, null, options);\n            \n        }\n    }    \n    \n    LGraphNode.prototype.changeMode = function(modeTo){\n        switch(modeTo){\n            case LiteGraph.ON_EVENT:\n                // this.addOnExecutedOutput();\n                break;\n                \n            case LiteGraph.ON_TRIGGER:\n                this.addOnTriggerInput();\n                this.addOnExecutedOutput();\n                break;\n                \n            case LiteGraph.NEVER:\n                break;\n                \n            case LiteGraph.ALWAYS:\n                break;\n                \n            case LiteGraph.ON_REQUEST:\n                break;\n            \n            default:\n                return false;\n                break;\n        }\n        this.mode = modeTo;\n        return true;\n    };\n\n    /**\n     * Triggers the execution of actions that were deferred when the action was triggered\n     * @method executePendingActions\n     */    \n    LGraphNode.prototype.executePendingActions = function() {\n        if(!this._waiting_actions || !this._waiting_actions.length)\n            return;\n        for(var i = 0; i < this._waiting_actions.length;++i)\n        {\n            var p = this._waiting_actions[i];\n            this.onAction(p[0],p[1],p[2],p[3],p[4]);\n        }        \n        this._waiting_actions.length = 0;\n    }\n\n    \n    /**\n     * Triggers the node code execution, place a boolean/counter to mark the node as being executed\n     * @method doExecute\n     * @param {*} param\n     * @param {*} options\n     */\n    LGraphNode.prototype.doExecute = function(param, options) {\n        options = options || {};\n        if (this.onExecute){\n            \n            // enable this to give the event an ID\n\t\t\tif (!options.action_call) options.action_call = this.id+\"_exec_\"+Math.floor(Math.random()*9999);\n            \n            this.graph.nodes_executing[this.id] = true; //.push(this.id);\n\n            this.onExecute(param, options);\n            \n            this.graph.nodes_executing[this.id] = false; //.pop();\n            \n            // save execution/action ref\n            this.exec_version = this.graph.iteration;\n            if(options && options.action_call){\n                this.action_call = options.action_call; // if (param)\n                this.graph.nodes_executedAction[this.id] = options.action_call;\n            }\n        }\n        else {\n        }\n        this.execute_triggered = 2; // the nFrames it will be used (-- each step), means \"how old\" is the event\n        if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback\n    };\n    \n    /**\n     * Triggers an action, wrapped by logics to control execution flow\n     * @method actionDo\n     * @param {String} action name\n     * @param {*} param\n     */\n    LGraphNode.prototype.actionDo = function(action, param, options, action_slot ) {\n        options = options || {};\n        if (this.onAction){\n            \n\t\t\t// enable this to give the event an ID\n            if (!options.action_call) options.action_call = this.id+\"_\"+(action?action:\"action\")+\"_\"+Math.floor(Math.random()*9999);\n            \n            this.graph.nodes_actioning[this.id] = (action?action:\"actioning\"); //.push(this.id);\n            \n            this.onAction(action, param, options, action_slot);\n            \n            this.graph.nodes_actioning[this.id] = false; //.pop();\n            \n            // save execution/action ref\n            if(options && options.action_call){\n                this.action_call = options.action_call; // if (param)\n                this.graph.nodes_executedAction[this.id] = options.action_call;\n            }\n        }\n        this.action_triggered = 2; // the nFrames it will be used (-- each step), means \"how old\" is the event\n        if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options);\n    };\n    \n    /**\n     * Triggers an event in this node, this will trigger any output with the same name\n     * @method trigger\n     * @param {String} event name ( \"on_play\", ... ) if action is equivalent to false then the event is send to all\n     * @param {*} param\n     */\n    LGraphNode.prototype.trigger = function(action, param, options) {\n        if (!this.outputs || !this.outputs.length) {\n            return;\n        }\n\n        if (this.graph)\n            this.graph._last_trigger_time = LiteGraph.getTime();\n\n        for (var i = 0; i < this.outputs.length; ++i) {\n            var output = this.outputs[i];\n            if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) )\n                continue;\n            this.triggerSlot(i, param, null, options);\n        }\n    };\n\n    /**\n     * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes\n     * @method triggerSlot\n     * @param {Number} slot the index of the output slot\n     * @param {*} param\n     * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot\n     */\n    LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) {\n        options = options || {};\n        if (!this.outputs) {\n            return;\n        }\n\n\t\tif(slot == null)\n\t\t{\n\t\t\tconsole.error(\"slot must be a number\");\n\t\t\treturn;\n\t\t}\n\n\t\tif(slot.constructor !== Number)\n\t\t\tconsole.warn(\"slot must be a number, use node.trigger('name') if you want to use a string\");\n\n        var output = this.outputs[slot];\n        if (!output) {\n            return;\n        }\n\n        var links = output.links;\n        if (!links || !links.length) {\n            return;\n        }\n\n        if (this.graph) {\n            this.graph._last_trigger_time = LiteGraph.getTime();\n        }\n\n        //for every link attached here\n        for (var k = 0; k < links.length; ++k) {\n            var id = links[k];\n            if (link_id != null && link_id != id) {\n                //to skip links\n                continue;\n            }\n            var link_info = this.graph.links[links[k]];\n            if (!link_info) {\n                //not connected\n                continue;\n            }\n            link_info._last_time = LiteGraph.getTime();\n            var node = this.graph.getNodeById(link_info.target_id);\n            if (!node) {\n                //node not found?\n                continue;\n            }\n\n            //used to mark events in graph\n            var target_connection = node.inputs[link_info.target_slot];\n\n\t\t\tif (node.mode === LiteGraph.ON_TRIGGER)\n\t\t\t{\n\t\t\t\t// generate unique trigger ID if not present\n\t\t\t\tif (!options.action_call) options.action_call = this.id+\"_trigg_\"+Math.floor(Math.random()*9999);\n                if (node.onExecute) {\n                    // -- wrapping node.onExecute(param); --\n                    node.doExecute(param, options);\n                }\n\t\t\t}\n\t\t\telse if (node.onAction) {\n                // generate unique action ID if not present\n\t\t\t\tif (!options.action_call) options.action_call = this.id+\"_act_\"+Math.floor(Math.random()*9999);\n                //pass the action name\n                var target_connection = node.inputs[link_info.target_slot];\n\n                //instead of executing them now, it will be executed in the next graph loop, to ensure data flow\n                if(LiteGraph.use_deferred_actions && node.onExecute)\n                {\n                    if(!node._waiting_actions)\n                        node._waiting_actions = [];\n                    node._waiting_actions.push([target_connection.name, param, options, link_info.target_slot]);\n                }\n                else\n                {\n                    // wrap node.onAction(target_connection.name, param);\n                    node.actionDo( target_connection.name, param, options, link_info.target_slot );\n                }\n            }\n        }\n    };\n\n    /**\n     * clears the trigger slot animation\n     * @method clearTriggeredSlot\n     * @param {Number} slot the index of the output slot\n     * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot\n     */\n    LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) {\n        if (!this.outputs) {\n            return;\n        }\n\n        var output = this.outputs[slot];\n        if (!output) {\n            return;\n        }\n\n        var links = output.links;\n        if (!links || !links.length) {\n            return;\n        }\n\n        //for every link attached here\n        for (var k = 0; k < links.length; ++k) {\n            var id = links[k];\n            if (link_id != null && link_id != id) {\n                //to skip links\n                continue;\n            }\n            var link_info = this.graph.links[links[k]];\n            if (!link_info) {\n                //not connected\n                continue;\n            }\n            link_info._last_time = 0;\n        }\n    };\n\n    /**\n     * changes node size and triggers callback\n     * @method setSize\n     * @param {vec2} size\n     */\n    LGraphNode.prototype.setSize = function(size)\n\t{\n\t\tthis.size = size;\n\t\tif(this.onResize)\n\t\t\tthis.onResize(this.size);\n\t}\n\n    /**\n     * add a new property to this node\n     * @method addProperty\n     * @param {string} name\n     * @param {*} default_value\n     * @param {string} type string defining the output type (\"vec3\",\"number\",...)\n     * @param {Object} extra_info this can be used to have special properties of the property (like values, etc)\n     */\n    LGraphNode.prototype.addProperty = function(\n        name,\n        default_value,\n        type,\n        extra_info\n    ) {\n        var o = { name: name, type: type, default_value: default_value };\n        if (extra_info) {\n            for (var i in extra_info) {\n                o[i] = extra_info[i];\n            }\n        }\n        if (!this.properties_info) {\n            this.properties_info = [];\n        }\n        this.properties_info.push(o);\n        if (!this.properties) {\n            this.properties = {};\n        }\n        this.properties[name] = default_value;\n        return o;\n    };\n\n    //connections\n\n    /**\n     * add a new output slot to use in this node\n     * @method addOutput\n     * @param {string} name\n     * @param {string} type string defining the output type (\"vec3\",\"number\",...)\n     * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc)\n     */\n    LGraphNode.prototype.addOutput = function(name, type, extra_info) {\n        var output = { name: name, type: type, links: null };\n        if (extra_info) {\n            for (var i in extra_info) {\n                output[i] = extra_info[i];\n            }\n        }\n\n        if (!this.outputs) {\n            this.outputs = [];\n        }\n        this.outputs.push(output);\n        if (this.onOutputAdded) {\n            this.onOutputAdded(output);\n        }\n        \n        if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true);\n        \n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n        return output;\n    };\n\n    /**\n     * add a new output slot to use in this node\n     * @method addOutputs\n     * @param {Array} array of triplets like [[name,type,extra_info],[...]]\n     */\n    LGraphNode.prototype.addOutputs = function(array) {\n        for (var i = 0; i < array.length; ++i) {\n            var info = array[i];\n            var o = { name: info[0], type: info[1], link: null };\n            if (array[2]) {\n                for (var j in info[2]) {\n                    o[j] = info[2][j];\n                }\n            }\n\n            if (!this.outputs) {\n                this.outputs = [];\n            }\n            this.outputs.push(o);\n            if (this.onOutputAdded) {\n                this.onOutputAdded(o);\n            }\n            \n            if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true);\n            \n        }\n\n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * remove an existing output slot\n     * @method removeOutput\n     * @param {number} slot\n     */\n    LGraphNode.prototype.removeOutput = function(slot) {\n        this.disconnectOutput(slot);\n        this.outputs.splice(slot, 1);\n        for (var i = slot; i < this.outputs.length; ++i) {\n            if (!this.outputs[i] || !this.outputs[i].links) {\n                continue;\n            }\n            var links = this.outputs[i].links;\n            for (var j = 0; j < links.length; ++j) {\n                var link = this.graph.links[links[j]];\n                if (!link) {\n                    continue;\n                }\n                link.origin_slot -= 1;\n            }\n        }\n\n        this.setSize( this.computeSize() );\n        if (this.onOutputRemoved) {\n            this.onOutputRemoved(slot);\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * add a new input slot to use in this node\n     * @method addInput\n     * @param {string} name\n     * @param {string} type string defining the input type (\"vec3\",\"number\",...), it its a generic one use 0\n     * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc)\n     */\n    LGraphNode.prototype.addInput = function(name, type, extra_info) {\n        type = type || 0;\n        var input = { name: name, type: type, link: null };\n        if (extra_info) {\n            for (var i in extra_info) {\n                input[i] = extra_info[i];\n            }\n        }\n\n        if (!this.inputs) {\n            this.inputs = [];\n        }\n\n        this.inputs.push(input);\n        this.setSize( this.computeSize() );\n\n        if (this.onInputAdded) {\n            this.onInputAdded(input);\n\t\t}\n        \n        LiteGraph.registerNodeAndSlotType(this,type);\n\n        this.setDirtyCanvas(true, true);\n        return input;\n    };\n\n    /**\n     * add several new input slots in this node\n     * @method addInputs\n     * @param {Array} array of triplets like [[name,type,extra_info],[...]]\n     */\n    LGraphNode.prototype.addInputs = function(array) {\n        for (var i = 0; i < array.length; ++i) {\n            var info = array[i];\n            var o = { name: info[0], type: info[1], link: null };\n            if (array[2]) {\n                for (var j in info[2]) {\n                    o[j] = info[2][j];\n                }\n            }\n\n            if (!this.inputs) {\n                this.inputs = [];\n            }\n            this.inputs.push(o);\n            if (this.onInputAdded) {\n                this.onInputAdded(o);\n            }\n            \n            LiteGraph.registerNodeAndSlotType(this,info[1]);\n        }\n\n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * remove an existing input slot\n     * @method removeInput\n     * @param {number} slot\n     */\n    LGraphNode.prototype.removeInput = function(slot) {\n        this.disconnectInput(slot);\n        var slot_info = this.inputs.splice(slot, 1);\n        for (var i = slot; i < this.inputs.length; ++i) {\n            if (!this.inputs[i]) {\n                continue;\n            }\n            var link = this.graph.links[this.inputs[i].link];\n            if (!link) {\n                continue;\n            }\n            link.target_slot -= 1;\n        }\n        this.setSize( this.computeSize() );\n        if (this.onInputRemoved) {\n            this.onInputRemoved(slot, slot_info[0] );\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * add an special connection to this node (used for special kinds of graphs)\n     * @method addConnection\n     * @param {string} name\n     * @param {string} type string defining the input type (\"vec3\",\"number\",...)\n     * @param {[x,y]} pos position of the connection inside the node\n     * @param {string} direction if is input or output\n     */\n    LGraphNode.prototype.addConnection = function(name, type, pos, direction) {\n        var o = {\n            name: name,\n            type: type,\n            pos: pos,\n            direction: direction,\n            links: null\n        };\n        this.connections.push(o);\n        return o;\n    };\n\n    /**\n     * computes the minimum size of a node according to its inputs and output slots\n     * @method computeSize\n     * @param {vec2} minHeight\n     * @return {vec2} the total size\n     */\n    LGraphNode.prototype.computeSize = function(out) {\n        if (this.constructor.size) {\n            return this.constructor.size.concat();\n        }\n\n        var rows = Math.max(\n            this.inputs ? this.inputs.length : 1,\n            this.outputs ? this.outputs.length : 1\n        );\n        var size = out || new Float32Array([0, 0]);\n        rows = Math.max(rows, 1);\n        var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size\n\n        var title_width = compute_text_size(this.title);\n        var input_width = 0;\n        var output_width = 0;\n\n        if (this.inputs) {\n            for (var i = 0, l = this.inputs.length; i < l; ++i) {\n                var input = this.inputs[i];\n                var text = input.label || input.name || \"\";\n                var text_width = compute_text_size(text);\n                if (input_width < text_width) {\n                    input_width = text_width;\n                }\n            }\n        }\n\n        if (this.outputs) {\n            for (var i = 0, l = this.outputs.length; i < l; ++i) {\n                var output = this.outputs[i];\n                var text = output.label || output.name || \"\";\n                var text_width = compute_text_size(text);\n                if (output_width < text_width) {\n                    output_width = text_width;\n                }\n            }\n        }\n\n        size[0] = Math.max(input_width + output_width + 10, title_width);\n        size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH);\n        if (this.widgets && this.widgets.length) {\n            size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5);\n        }\n\n        size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT;\n\n        var widgets_height = 0;\n        if (this.widgets && this.widgets.length) {\n            for (var i = 0, l = this.widgets.length; i < l; ++i) {\n                if (this.widgets[i].computeSize)\n                    widgets_height += this.widgets[i].computeSize(size[0])[1] + 4;\n                else\n                    widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4;\n            }\n            widgets_height += 8;\n        }\n\n        //compute height using widgets height\n        if( this.widgets_up )\n            size[1] = Math.max( size[1], widgets_height );\n        else if( this.widgets_start_y != null )\n            size[1] = Math.max( size[1], widgets_height + this.widgets_start_y );\n        else\n            size[1] += widgets_height;\n\n        function compute_text_size(text) {\n            if (!text) {\n                return 0;\n            }\n            return font_size * text.length * 0.6;\n        }\n\n        if (\n            this.constructor.min_height &&\n            size[1] < this.constructor.min_height\n        ) {\n            size[1] = this.constructor.min_height;\n        }\n\n        size[1] += 6; //margin\n\n        return size;\n    };\n\n    /**\n     * returns all the info available about a property of this node.\n     *\n     * @method getPropertyInfo\n     * @param {String} property name of the property\n     * @return {Object} the object with all the available info\n    */\n    LGraphNode.prototype.getPropertyInfo = function( property )\n\t{\n        var info = null;\n\n\t\t//there are several ways to define info about a property\n\t\t//legacy mode\n\t\tif (this.properties_info) {\n            for (var i = 0; i < this.properties_info.length; ++i) {\n                if (this.properties_info[i].name == property) {\n                    info = this.properties_info[i];\n                    break;\n                }\n            }\n        }\n\t\t//litescene mode using the constructor\n\t\tif(this.constructor[\"@\" + property])\n\t\t\tinfo = this.constructor[\"@\" + property];\n\n\t\tif(this.constructor.widgets_info && this.constructor.widgets_info[property])\n\t\t\tinfo = this.constructor.widgets_info[property];\n\n\t\t//litescene mode using the constructor\n\t\tif (!info && this.onGetPropertyInfo) {\n            info = this.onGetPropertyInfo(property);\n        }\n\n        if (!info)\n            info = {};\n\t\tif(!info.type)\n\t\t\tinfo.type = typeof this.properties[property];\n\t\tif(info.widget == \"combo\")\n\t\t\tinfo.type = \"enum\";\n\n\t\treturn info;\n\t}\n\n    /**\n     * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties\n     *\n     * @method addWidget\n     * @param {String} type the widget type (could be \"number\",\"string\",\"combo\"\n     * @param {String} name the text to show on the widget\n     * @param {String} value the default value\n     * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify)\n     * @param {Object} options the object that contains special properties of this widget \n     * @return {Object} the created widget object\n     */\n    LGraphNode.prototype.addWidget = function( type, name, value, callback, options )\n\t{\n        if (!this.widgets) {\n            this.widgets = [];\n        }\n\n\t\tif(!options && callback && callback.constructor === Object)\n\t\t{\n\t\t\toptions = callback;\n\t\t\tcallback = null;\n\t\t}\n\n\t\tif(options && options.constructor === String) //options can be the property name\n\t\t\toptions = { property: options };\n\n\t\tif(callback && callback.constructor === String) //callback can be the property name\n\t\t{\n\t\t\tif(!options)\n\t\t\t\toptions = {};\n\t\t\toptions.property = callback;\n\t\t\tcallback = null;\n\t\t}\n\n\t\tif(callback && callback.constructor !== Function)\n\t\t{\n\t\t\tconsole.warn(\"addWidget: callback must be a function\");\n\t\t\tcallback = null;\n\t\t}\n\n        var w = {\n            type: type.toLowerCase(),\n            name: name,\n            value: value,\n            callback: callback,\n            options: options || {}\n        };\n\n        if (w.options.y !== undefined) {\n            w.y = w.options.y;\n        }\n\n        if (!callback && !w.options.callback && !w.options.property) {\n            console.warn(\"LiteGraph addWidget(...) without a callback or property assigned\");\n        }\n        if (type == \"combo\" && !w.options.values) {\n            throw \"LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }\";\n        }\n        this.widgets.push(w);\n\t\tthis.setSize( this.computeSize() );\n        return w;\n    };\n\n    LGraphNode.prototype.addCustomWidget = function(custom_widget) {\n        if (!this.widgets) {\n            this.widgets = [];\n        }\n        this.widgets.push(custom_widget);\n        return custom_widget;\n    };\n\n    /**\n     * returns the bounding of the object, used for rendering purposes\n     * @method getBounding\n     * @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage\n     * @param compute_outer {boolean?} [optional] set to true to include the shadow and connection points in the bounding calculation\n     * @return {Float32Array[4]} the bounding box in format of [topleft_cornerx, topleft_cornery, width, height]\n     */\n    LGraphNode.prototype.getBounding = function(out, compute_outer) {\n        out = out || new Float32Array(4);\n        const nodePos = this.pos;\n        const isCollapsed = this.flags.collapsed;\n        const nodeSize = this.size;\n        \n        let left_offset = 0;\n        // 1 offset due to how nodes are rendered\n        let right_offset =  1 ;\n        let top_offset = 0;\n        let bottom_offset = 0;\n        \n        if (compute_outer) {\n            // 4 offset for collapsed node connection points\n            left_offset = 4;\n            // 6 offset for right shadow and collapsed node connection points\n            right_offset = 6 + left_offset;\n            // 4 offset for collapsed nodes top connection points\n            top_offset = 4;\n            // 5 offset for bottom shadow and collapsed node connection points\n            bottom_offset = 5 + top_offset;\n        }\n        \n        out[0] = nodePos[0] - left_offset;\n        out[1] = nodePos[1] - LiteGraph.NODE_TITLE_HEIGHT - top_offset;\n        out[2] = isCollapsed ?\n            (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + right_offset :\n            nodeSize[0] + right_offset;\n        out[3] = isCollapsed ?\n            LiteGraph.NODE_TITLE_HEIGHT + bottom_offset :\n            nodeSize[1] + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset;\n\n        if (this.onBounding) {\n            this.onBounding(out);\n        }\n        return out;\n    };\n\n    /**\n     * checks if a point is inside the shape of a node\n     * @method isPointInside\n     * @param {number} x\n     * @param {number} y\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) {\n        margin = margin || 0;\n\n        var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT;\n        if (skip_title) {\n            margin_top = 0;\n        }\n        if (this.flags && this.flags.collapsed) {\n            //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS)\n            if (\n                isInsideRectangle(\n                    x,\n                    y,\n                    this.pos[0] - margin,\n                    this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin,\n                    (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) +\n                        2 * margin,\n                    LiteGraph.NODE_TITLE_HEIGHT + 2 * margin\n                )\n            ) {\n                return true;\n            }\n        } else if (\n            this.pos[0] - 4 - margin < x &&\n            this.pos[0] + this.size[0] + 4 + margin > x &&\n            this.pos[1] - margin_top - margin < y &&\n            this.pos[1] + this.size[1] + margin > y\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    /**\n     * checks if a point is inside a node slot, and returns info about which slot\n     * @method getSlotInPosition\n     * @param {number} x\n     * @param {number} y\n     * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }\n     */\n    LGraphNode.prototype.getSlotInPosition = function(x, y) {\n        //search for inputs\n        var link_pos = new Float32Array(2);\n        if (this.inputs) {\n            for (var i = 0, l = this.inputs.length; i < l; ++i) {\n                var input = this.inputs[i];\n                this.getConnectionPos(true, i, link_pos);\n                if (\n                    isInsideRectangle(\n                        x,\n                        y,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        20,\n                        10\n                    )\n                ) {\n                    return { input: input, slot: i, link_pos: link_pos };\n                }\n            }\n        }\n\n        if (this.outputs) {\n            for (var i = 0, l = this.outputs.length; i < l; ++i) {\n                var output = this.outputs[i];\n                this.getConnectionPos(false, i, link_pos);\n                if (\n                    isInsideRectangle(\n                        x,\n                        y,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        20,\n                        10\n                    )\n                ) {\n                    return { output: output, slot: i, link_pos: link_pos };\n                }\n            }\n        }\n\n        return null;\n    };\n\n    /**\n     * returns the input slot with a given name (used for dynamic slots), -1 if not found\n     * @method findInputSlot\n     * @param {string} name the name of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findInputSlot = function(name,  returnObj) {\n        if (!this.inputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            if (name == this.inputs[i].name) {\n                return !returnObj ? i : this.inputs[i];\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * returns the output slot with a given name (used for dynamic slots), -1 if not found\n     * @method findOutputSlot\n     * @param {string} name the name of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findOutputSlot = function(name, returnObj) {\n        returnObj = returnObj || false;\n        if (!this.outputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            if (name == this.outputs[i].name) {\n                return !returnObj ? i : this.outputs[i];\n            }\n        }\n        return -1;\n    };\n    \n    // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options\n    \n    /**\n     * returns the first free input slot\n     * @method findInputSlotFree\n     * @param {object} options\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findInputSlotFree = function(optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = {returnObj: false\n                        ,typesNotAccepted: []\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (!this.inputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            if (this.inputs[i].link && this.inputs[i].link != null) {\n                continue;\n            }\n            if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){\n                continue;\n            }\n            return !opts.returnObj ? i : this.inputs[i];\n        }\n        return -1;\n    };\n\n    /**\n     * returns the first output slot free\n     * @method findOutputSlotFree\n     * @param {object} options\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findOutputSlotFree = function(optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { returnObj: false\n                        ,typesNotAccepted: []\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (!this.outputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            if (this.outputs[i].links && this.outputs[i].links != null) {\n                continue;\n            }\n            if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){\n                continue;\n            }\n            return !opts.returnObj ? i : this.outputs[i];\n        }\n        return -1;\n    };\n    \n    /**\n     * findSlotByType for INPUTS\n     */\n    LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied);\n    };\n\n    /**\n     * findSlotByType for OUTPUTS\n     */\n    LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied);\n    };\n    \n    /**\n     * returns the output (or input) slot with a given type, -1 if not found\n     * @method findSlotByType\n     * @param {boolean} input uise inputs instead of outputs\n     * @param {string} type the type of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway)\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        input = input || false;\n        returnObj = returnObj || false;\n        preferFreeSlot = preferFreeSlot || false;\n        doNotUseOccupied = doNotUseOccupied || false;\n        var aSlots = input ? this.inputs : this.outputs;\n        if (!aSlots) {\n            return -1;\n        }\n\t\t// !! empty string type is considered 0, * !!\n\t\tif (type == \"\" || type == \"*\") type = 0; \n        for (var i = 0, l = aSlots.length; i < l; ++i) {\n            var tFound = false;\n            var aSource = (type+\"\").toLowerCase().split(\",\");\n            var aDest = aSlots[i].type==\"0\"||aSlots[i].type==\"*\"?\"0\":aSlots[i].type;\n\t\t\taDest = (aDest+\"\").toLowerCase().split(\",\");\n            for(var sI=0;sI<aSource.length;sI++){\n                for(var dI=0;dI<aDest.length;dI++){\n\t\t\t\t\tif (aSource[sI]==\"_event_\") aSource[sI] = LiteGraph.EVENT;\n\t\t\t\t\tif (aDest[sI]==\"_event_\") aDest[sI] = LiteGraph.EVENT;\n\t\t\t\t\tif (aSource[sI]==\"*\") aSource[sI] = 0;\n\t\t\t\t\tif (aDest[sI]==\"*\") aDest[sI] = 0;\n\t\t\t\t\tif (aSource[sI] == aDest[dI]) {\n                        if (preferFreeSlot && aSlots[i].links && aSlots[i].links !== null) continue;\n                        return !returnObj ? i : aSlots[i];\n                    }\n                }\n            }\n        }\n        // if didnt find some, stop checking for free slots\n        if (preferFreeSlot && !doNotUseOccupied){\n            for (var i = 0, l = aSlots.length; i < l; ++i) {\n                var tFound = false;\n                var aSource = (type+\"\").toLowerCase().split(\",\");\n                var aDest = aSlots[i].type==\"0\"||aSlots[i].type==\"*\"?\"0\":aSlots[i].type;\n\t\t\t\taDest = (aDest+\"\").toLowerCase().split(\",\");\n                for(var sI=0;sI<aSource.length;sI++){\n                    for(var dI=0;dI<aDest.length;dI++){\n\t\t\t\t\t\tif (aSource[sI]==\"*\") aSource[sI] = 0;\n\t\t\t\t\t\tif (aDest[sI]==\"*\") aDest[sI] = 0;\n                        if (aSource[sI] == aDest[dI]) {\n                            return !returnObj ? i : aSlots[i];\n                        }\n                    }\n                }\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * connect this node output to the input of another node BY TYPE\n     * @method connectByType\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {string} target_type the input slot type of the target node\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connectByType = function(slot, target_node, target_slotType, optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { createEventInCase: true\n\t\t\t\t\t   \t,firstFreeIfOutputGeneralInCase: true\n                        ,generalTypeInCase: true\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (target_node && target_node.constructor === Number) {\n            target_node = this.graph.getNodeById(target_node);\n        }\n        var target_slot = target_node.findInputSlotByType(target_slotType, false, true);\n        if (target_slot >= 0 && target_slot !== null){\n            //console.debug(\"CONNbyTYPE type \"+target_slotType+\" for \"+target_slot)\n            return this.connect(slot, target_node, target_slot);\n        }else{\n            //console.log(\"type \"+target_slotType+\" not found or not free?\")\n            if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){\n                // WILL CREATE THE onTrigger IN SLOT\n\t\t\t\t//console.debug(\"connect WILL CREATE THE onTrigger \"+target_slotType+\" to \"+target_node);\n                return this.connect(slot, target_node, -1);\n            }\n\t\t\t// connect to the first general output slot if not found a specific type and \n            if (opts.generalTypeInCase){\n                var target_slot = target_node.findInputSlotByType(0, false, true, true);\n\t\t\t\t//console.debug(\"connect TO a general type (*, 0), if not found the specific type \",target_slotType,\" to \",target_node,\"RES_SLOT:\",target_slot);\n                if (target_slot >= 0){\n                    return this.connect(slot, target_node, target_slot);\n                }\n            }\n            // connect to the first free input slot if not found a specific type and this output is general\n            if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == \"*\" || target_slotType == \"\")){\n                var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] });\n\t\t\t\t//console.debug(\"connect TO TheFirstFREE \",target_slotType,\" to \",target_node,\"RES_SLOT:\",target_slot);\n                if (target_slot >= 0){\n\t\t\t\t\treturn this.connect(slot, target_node, target_slot);\n                }\n            }\n\t\t\t\n\t\t\tconsole.debug(\"no way to connect type: \",target_slotType,\" to targetNODE \",target_node);\n\t\t\t//TODO filter\n\t\t\t\n            return null;\n        }\n    }\n    \n    /**\n     * connect this node input to the output of another node BY TYPE\n     * @method connectByType\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {string} target_type the output slot type of the target node\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { createEventInCase: true\n                        ,firstFreeIfInputGeneralInCase: true\n                        ,generalTypeInCase: true\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (source_node && source_node.constructor === Number) {\n            source_node = this.graph.getNodeById(source_node);\n        }\n        var source_slot = source_node.findOutputSlotByType(source_slotType, false, true);\n        if (source_slot >= 0 && source_slot !== null){\n            //console.debug(\"CONNbyTYPE OUT! type \"+source_slotType+\" for \"+source_slot)\n            return source_node.connect(source_slot, this, slot);\n        }else{\n            \n            // connect to the first general output slot if not found a specific type and \n            if (opts.generalTypeInCase){\n                var source_slot = source_node.findOutputSlotByType(0, false, true, true);\n                if (source_slot >= 0){\n                    return source_node.connect(source_slot, this, slot);\n                }\n            }\n            \n            if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){\n                // WILL CREATE THE onExecuted OUT SLOT\n\t\t\t\tif (LiteGraph.do_add_triggers_slots){\n\t\t\t\t\tvar source_slot = source_node.addOnExecutedOutput();\n\t\t\t\t\treturn source_node.connect(source_slot, this, slot);\n\t\t\t\t}\n            }\n            // connect to the first free output slot if not found a specific type and this input is general\n            if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == \"*\" || source_slotType == \"\")){\n                var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] });\n                if (source_slot >= 0){\n                    return source_node.connect(source_slot, this, slot);\n                }\n            }\n            \n\t\t\tconsole.debug(\"no way to connect byOUT type: \",source_slotType,\" to sourceNODE \",source_node);\n\t\t\t//TODO filter\n\t\t\t\n            //console.log(\"type OUT! \"+source_slotType+\" not found or not free?\")\n            return null;\n        }\n    }\n    \n    /**\n     * connect this node output to the input of another node\n     * @method connect\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connect = function(slot, target_node, target_slot) {\n        target_slot = target_slot || 0;\n\n        if (!this.graph) {\n            //could be connected before adding it to a graph\n            console.log(\n                \"Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them.\"\n            ); //due to link ids being associated with graphs\n            return null;\n        }\n\n        //seek for the output slot\n        if (slot.constructor === String) {\n            slot = this.findOutputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return null;\n            }\n        } else if (!this.outputs || slot >= this.outputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return null;\n        }\n\n        if (target_node && target_node.constructor === Number) {\n            target_node = this.graph.getNodeById(target_node);\n        }\n        if (!target_node) {\n            throw \"target node is null\";\n        }\n\n        //avoid loopback\n        if (target_node == this) {\n            return null;\n        }\n\n        //you can specify the slot by name\n        if (target_slot.constructor === String) {\n            target_slot = target_node.findInputSlot(target_slot);\n            if (target_slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\n                        \"Connect: Error, no slot of name \" + target_slot\n                    );\n                }\n                return null;\n            }\n        } else if (target_slot === LiteGraph.EVENT) {\n            \n            if (LiteGraph.do_add_triggers_slots){\n\t            //search for first slot with event? :: NO this is done outside\n\t\t\t\t//console.log(\"Connect: Creating triggerEvent\");\n\t            // force mode\n\t            target_node.changeMode(LiteGraph.ON_TRIGGER);\n\t            target_slot = target_node.findInputSlot(\"onTrigger\");\n        \t}else{\n            \treturn null; // -- break --\n\t\t\t}\n        } else if (\n            !target_node.inputs ||\n            target_slot >= target_node.inputs.length\n        ) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return null;\n        }\n\n\t\tvar changed = false;\n\n        var input = target_node.inputs[target_slot];\n        var link_info = null;\n        var output = this.outputs[slot];\n        \n        if (!this.outputs[slot]){\n            /*console.debug(\"Invalid slot passed: \"+slot);\n            console.debug(this.outputs);*/\n            return null;\n        }\n\n        // allow target node to change slot\n        if (target_node.onBeforeConnectInput) {\n            // This way node can choose another slot (or make a new one?)\n            target_slot = target_node.onBeforeConnectInput(target_slot); //callback\n        }\n\n\t\t//check target_slot and check connection types\n        if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type))\n\t\t{\n\t        this.setDirtyCanvas(false, true);\n\t\t\tif(changed)\n\t\t        this.graph.connectionChange(this, link_info);\n\t\t\treturn null;\n\t\t}else{\n\t\t\t//console.debug(\"valid connection\",output.type, input.type);\n\t\t}\n\n        //allows nodes to block connection, callback\n        if (target_node.onConnectInput) {\n            if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) {\n                return null;\n            }\n        }\n        if (this.onConnectOutput) { // callback\n            if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) {\n                return null;\n            }\n        }\n\n        //if there is something already plugged there, disconnect\n        if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) {\n\t\t\tthis.graph.beforeChange();\n            target_node.disconnectInput(target_slot, {doProcessChange: false});\n\t\t\tchanged = true;\n        }\n        if (output.links !== null && output.links.length){\n            switch(output.type){\n                case LiteGraph.EVENT:\n                    if (!LiteGraph.allow_multi_output_for_events){\n                        this.graph.beforeChange();\n                        this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false});\n                        changed = true;\n                    }\n                break;\n                default:\n                break;\n            }\n        }\n\n        var nextId\n        if (LiteGraph.use_uuids)\n            nextId = LiteGraph.uuidv4();\n        else\n            nextId = ++this.graph.last_link_id;\n        \n\t\t//create link class\n\t\tlink_info = new LLink(\n\t\t\tnextId,\n\t\t\tinput.type || output.type,\n\t\t\tthis.id,\n\t\t\tslot,\n\t\t\ttarget_node.id,\n\t\t\ttarget_slot\n\t\t);\n\n\t\t//add to graph links list\n\t\tthis.graph.links[link_info.id] = link_info;\n\n\t\t//connect in output\n\t\tif (output.links == null) {\n\t\t\toutput.links = [];\n\t\t}\n\t\toutput.links.push(link_info.id);\n\t\t//connect in input\n\t\ttarget_node.inputs[target_slot].link = link_info.id;\n\t\tif (this.graph) {\n\t\t\tthis.graph._version++;\n\t\t}\n\t\tif (this.onConnectionsChange) {\n\t\t\tthis.onConnectionsChange(\n\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\tslot,\n\t\t\t\ttrue,\n\t\t\t\tlink_info,\n\t\t\t\toutput\n\t\t\t);\n\t\t} //link_info has been created now, so its updated\n\t\tif (target_node.onConnectionsChange) {\n\t\t\ttarget_node.onConnectionsChange(\n\t\t\t\tLiteGraph.INPUT,\n\t\t\t\ttarget_slot,\n\t\t\t\ttrue,\n\t\t\t\tlink_info,\n\t\t\t\tinput\n\t\t\t);\n\t\t}\n\t\tif (this.graph && this.graph.onNodeConnectionChange) {\n\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\tLiteGraph.INPUT,\n\t\t\t\ttarget_node,\n\t\t\t\ttarget_slot,\n\t\t\t\tthis,\n\t\t\t\tslot\n\t\t\t);\n\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\tthis,\n\t\t\t\tslot,\n\t\t\t\ttarget_node,\n\t\t\t\ttarget_slot\n\t\t\t);\n\t\t}\n\n        this.setDirtyCanvas(false, true);\n\t\tthis.graph.afterChange();\n\t\tthis.graph.connectionChange(this, link_info);\n\n        return link_info;\n    };\n\n    /**\n     * disconnect one output to an specific node\n     * @method disconnectOutput\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]\n     * @return {boolean} if it was disconnected successfully\n     */\n    LGraphNode.prototype.disconnectOutput = function(slot, target_node) {\n        if (slot.constructor === String) {\n            slot = this.findOutputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return false;\n            }\n        } else if (!this.outputs || slot >= this.outputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return false;\n        }\n\n        //get output slot\n        var output = this.outputs[slot];\n        if (!output || !output.links || output.links.length == 0) {\n            return false;\n        }\n\n        //one of the output links in this slot\n        if (target_node) {\n            if (target_node.constructor === Number) {\n                target_node = this.graph.getNodeById(target_node);\n            }\n            if (!target_node) {\n                throw \"Target Node not found\";\n            }\n\n            for (var i = 0, l = output.links.length; i < l; i++) {\n                var link_id = output.links[i];\n                var link_info = this.graph.links[link_id];\n\n                //is the link we are searching for...\n                if (link_info.target_id == target_node.id) {\n                    output.links.splice(i, 1); //remove here\n                    var input = target_node.inputs[link_info.target_slot];\n                    input.link = null; //remove there\n                    delete this.graph.links[link_id]; //remove the link from the links pool\n                    if (this.graph) {\n                        this.graph._version++;\n                    }\n                    if (target_node.onConnectionsChange) {\n                        target_node.onConnectionsChange(\n                            LiteGraph.INPUT,\n                            link_info.target_slot,\n                            false,\n                            link_info,\n                            input\n                        );\n                    } //link_info hasn't been modified so its ok\n                    if (this.onConnectionsChange) {\n                        this.onConnectionsChange(\n                            LiteGraph.OUTPUT,\n                            slot,\n                            false,\n                            link_info,\n                            output\n                        );\n                    }\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.OUTPUT,\n                            this,\n                            slot\n                        );\n                    }\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.OUTPUT,\n                            this,\n                            slot\n                        );\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.INPUT,\n                            target_node,\n                            link_info.target_slot\n                        );\n                    }\n                    break;\n                }\n            }\n        } //all the links in this output slot\n        else {\n            for (var i = 0, l = output.links.length; i < l; i++) {\n                var link_id = output.links[i];\n                var link_info = this.graph.links[link_id];\n                if (!link_info) {\n                    //bug: it happens sometimes\n                    continue;\n                }\n\n                var target_node = this.graph.getNodeById(link_info.target_id);\n                var input = null;\n                if (this.graph) {\n                    this.graph._version++;\n                }\n                if (target_node) {\n                    input = target_node.inputs[link_info.target_slot];\n                    input.link = null; //remove other side link\n                    if (target_node.onConnectionsChange) {\n                        target_node.onConnectionsChange(\n                            LiteGraph.INPUT,\n                            link_info.target_slot,\n                            false,\n                            link_info,\n                            input\n                        );\n                    } //link_info hasn't been modified so its ok\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.INPUT,\n                            target_node,\n                            link_info.target_slot\n                        );\n                    }\n                }\n                delete this.graph.links[link_id]; //remove the link from the links pool\n                if (this.onConnectionsChange) {\n                    this.onConnectionsChange(\n                        LiteGraph.OUTPUT,\n                        slot,\n                        false,\n                        link_info,\n                        output\n                    );\n                }\n                if (this.graph && this.graph.onNodeConnectionChange) {\n                    this.graph.onNodeConnectionChange(\n                        LiteGraph.OUTPUT,\n                        this,\n                        slot\n                    );\n                    this.graph.onNodeConnectionChange(\n                        LiteGraph.INPUT,\n                        target_node,\n                        link_info.target_slot\n                    );\n                }\n            }\n            output.links = null;\n        }\n\n        this.setDirtyCanvas(false, true);\n        this.graph.connectionChange(this);\n        return true;\n    };\n\n    /**\n     * disconnect one input\n     * @method disconnectInput\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @return {boolean} if it was disconnected successfully\n     */\n    LGraphNode.prototype.disconnectInput = function(slot) {\n        //seek for the output slot\n        if (slot.constructor === String) {\n            slot = this.findInputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return false;\n            }\n        } else if (!this.inputs || slot >= this.inputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return false;\n        }\n\n        var input = this.inputs[slot];\n        if (!input) {\n            return false;\n        }\n\n        var link_id = this.inputs[slot].link;\n\t\tif(link_id != null)\n\t\t{\n\t\t\tthis.inputs[slot].link = null;\n\n\t\t\t//remove other side\n\t\t\tvar link_info = this.graph.links[link_id];\n\t\t\tif (link_info) {\n\t\t\t\tvar target_node = this.graph.getNodeById(link_info.origin_id);\n\t\t\t\tif (!target_node) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tvar output = target_node.outputs[link_info.origin_slot];\n\t\t\t\tif (!output || !output.links || output.links.length == 0) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t//search in the inputs list for this link\n\t\t\t\tfor (var i = 0, l = output.links.length; i < l; i++) {\n\t\t\t\t\tif (output.links[i] == link_id) {\n\t\t\t\t\t\toutput.links.splice(i, 1);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tdelete this.graph.links[link_id]; //remove from the pool\n\t\t\t\tif (this.graph) {\n\t\t\t\t\tthis.graph._version++;\n\t\t\t\t}\n\t\t\t\tif (this.onConnectionsChange) {\n\t\t\t\t\tthis.onConnectionsChange(\n\t\t\t\t\t\tLiteGraph.INPUT,\n\t\t\t\t\t\tslot,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tlink_info,\n\t\t\t\t\t\tinput\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (target_node.onConnectionsChange) {\n\t\t\t\t\ttarget_node.onConnectionsChange(\n\t\t\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\t\t\ti,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tlink_info,\n\t\t\t\t\t\toutput\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (this.graph && this.graph.onNodeConnectionChange) {\n\t\t\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\t\t\ttarget_node,\n\t\t\t\t\t\ti\n\t\t\t\t\t);\n\t\t\t\t\tthis.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot);\n\t\t\t\t}\n\t\t\t}\n\t\t} //link != null\n\n        this.setDirtyCanvas(false, true);\n\t\tif(this.graph)\n\t        this.graph.connectionChange(this);\n        return true;\n    };\n\n    /**\n     * returns the center of a connection point in canvas coords\n     * @method getConnectionPos\n     * @param {boolean} is_input true if if a input slot, false if it is an output\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {vec2} out [optional] a place to store the output, to free garbage\n     * @return {[x,y]} the position\n     **/\n    LGraphNode.prototype.getConnectionPos = function(\n        is_input,\n        slot_number,\n        out\n    ) {\n        out = out || new Float32Array(2);\n        var num_slots = 0;\n        if (is_input && this.inputs) {\n            num_slots = this.inputs.length;\n        }\n        if (!is_input && this.outputs) {\n            num_slots = this.outputs.length;\n        }\n\n        var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n\n        if (this.flags.collapsed) {\n            var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;\n            if (this.horizontal) {\n                out[0] = this.pos[0] + w * 0.5;\n                if (is_input) {\n                    out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n                } else {\n                    out[1] = this.pos[1];\n                }\n            } else {\n                if (is_input) {\n                    out[0] = this.pos[0];\n                } else {\n                    out[0] = this.pos[0] + w;\n                }\n                out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            }\n            return out;\n        }\n\n        //weird feature that never got finished\n        if (is_input && slot_number == -1) {\n            out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            return out;\n        }\n\n        //hard-coded pos\n        if (\n            is_input &&\n            num_slots > slot_number &&\n            this.inputs[slot_number].pos\n        ) {\n            out[0] = this.pos[0] + this.inputs[slot_number].pos[0];\n            out[1] = this.pos[1] + this.inputs[slot_number].pos[1];\n            return out;\n        } else if (\n            !is_input &&\n            num_slots > slot_number &&\n            this.outputs[slot_number].pos\n        ) {\n            out[0] = this.pos[0] + this.outputs[slot_number].pos[0];\n            out[1] = this.pos[1] + this.outputs[slot_number].pos[1];\n            return out;\n        }\n\n        //horizontal distributed slots\n        if (this.horizontal) {\n            out[0] =\n                this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots);\n            if (is_input) {\n                out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n            } else {\n                out[1] = this.pos[1] + this.size[1];\n            }\n            return out;\n        }\n\n        //default vertical slots\n        if (is_input) {\n            out[0] = this.pos[0] + offset;\n        } else {\n            out[0] = this.pos[0] + this.size[0] + 1 - offset;\n        }\n        out[1] =\n            this.pos[1] +\n            (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT +\n            (this.constructor.slot_start_y || 0);\n        return out;\n    };\n\n    /* Force align to grid */\n    LGraphNode.prototype.alignToGrid = function() {\n        this.pos[0] =\n            LiteGraph.CANVAS_GRID_SIZE *\n            Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE);\n        this.pos[1] =\n            LiteGraph.CANVAS_GRID_SIZE *\n            Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE);\n    };\n\n    /* Console output */\n    LGraphNode.prototype.trace = function(msg) {\n        if (!this.console) {\n            this.console = [];\n        }\n\n        this.console.push(msg);\n        if (this.console.length > LGraphNode.MAX_CONSOLE) {\n            this.console.shift();\n        }\n\n\t\tif(this.graph.onNodeTrace)\n\t        this.graph.onNodeTrace(this, msg);\n    };\n\n    /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */\n    LGraphNode.prototype.setDirtyCanvas = function(\n        dirty_foreground,\n        dirty_background\n    ) {\n        if (!this.graph) {\n            return;\n        }\n        this.graph.sendActionToCanvas(\"setDirty\", [\n            dirty_foreground,\n            dirty_background\n        ]);\n    };\n\n    LGraphNode.prototype.loadImage = function(url) {\n        var img = new Image();\n        img.src = LiteGraph.node_images_path + url;\n        img.ready = false;\n\n        var that = this;\n        img.onload = function() {\n            this.ready = true;\n            that.setDirtyCanvas(true);\n        };\n        return img;\n    };\n\n    //safe LGraphNode action execution (not sure if safe)\n    /*\nLGraphNode.prototype.executeAction = function(action)\n{\n\tif(action == \"\") return false;\n\n\tif( action.indexOf(\";\") != -1 || action.indexOf(\"}\") != -1)\n\t{\n\t\tthis.trace(\"Error: Action contains unsafe characters\");\n\t\treturn false;\n\t}\n\n\tvar tokens = action.split(\"(\");\n\tvar func_name = tokens[0];\n\tif( typeof(this[func_name]) != \"function\")\n\t{\n\t\tthis.trace(\"Error: Action not found on node: \" + func_name);\n\t\treturn false;\n\t}\n\n\tvar code = action;\n\n\ttry\n\t{\n\t\tvar _foo = eval;\n\t\teval = null;\n\t\t(new Function(\"with(this) { \" + code + \"}\")).call(this);\n\t\teval = _foo;\n\t}\n\tcatch (err)\n\t{\n\t\tthis.trace(\"Error executing action {\" + action + \"} :\" + err);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n*/\n\n    /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */\n    LGraphNode.prototype.captureInput = function(v) {\n        if (!this.graph || !this.graph.list_of_graphcanvas) {\n            return;\n        }\n\n        var list = this.graph.list_of_graphcanvas;\n\n        for (var i = 0; i < list.length; ++i) {\n            var c = list[i];\n            //releasing somebody elses capture?!\n            if (!v && c.node_capturing_input != this) {\n                continue;\n            }\n\n            //change\n            c.node_capturing_input = v ? this : null;\n        }\n    };\n\n    /**\n     * Collapse the node to make it smaller on the canvas\n     * @method collapse\n     **/\n    LGraphNode.prototype.collapse = function(force) {\n        this.graph._version++;\n        if (this.constructor.collapsable === false && !force) {\n            return;\n        }\n        if (!this.flags.collapsed) {\n            this.flags.collapsed = true;\n        } else {\n            this.flags.collapsed = false;\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * Forces the node to do not move or realign on Z\n     * @method pin\n     **/\n\n    LGraphNode.prototype.pin = function(v) {\n        this.graph._version++;\n        if (v === undefined) {\n            this.flags.pinned = !this.flags.pinned;\n        } else {\n            this.flags.pinned = v;\n        }\n    };\n\n    LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) {\n        return [\n            (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0],\n            (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]\n        ];\n    };\n\n    function LGraphGroup(title) {\n        this._ctor(title);\n    }\n\n    global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup;\n\n    LGraphGroup.prototype._ctor = function(title) {\n        this.title = title || \"Group\";\n        this.font_size = 24;\n        this.color = LGraphCanvas.node_colors.pale_blue\n            ? LGraphCanvas.node_colors.pale_blue.groupcolor\n            : \"#AAA\";\n        this._bounding = new Float32Array([10, 10, 140, 80]);\n        this._pos = this._bounding.subarray(0, 2);\n        this._size = this._bounding.subarray(2, 4);\n        this._nodes = [];\n        this.graph = null;\n\n        Object.defineProperty(this, \"pos\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._pos[0] = v[0];\n                this._pos[1] = v[1];\n            },\n            get: function() {\n                return this._pos;\n            },\n            enumerable: true\n        });\n\n        Object.defineProperty(this, \"size\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._size[0] = Math.max(140, v[0]);\n                this._size[1] = Math.max(80, v[1]);\n            },\n            get: function() {\n                return this._size;\n            },\n            enumerable: true\n        });\n    };\n\n    LGraphGroup.prototype.configure = function(o) {\n        this.title = o.title;\n        this._bounding.set(o.bounding);\n        this.color = o.color;\n        this.font_size = o.font_size;\n    };\n\n    LGraphGroup.prototype.serialize = function() {\n        var b = this._bounding;\n        return {\n            title: this.title,\n            bounding: [\n                Math.round(b[0]),\n                Math.round(b[1]),\n                Math.round(b[2]),\n                Math.round(b[3])\n            ],\n            color: this.color,\n            font_size: this.font_size\n        };\n    };\n\n    LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) {\n        this._pos[0] += deltax;\n        this._pos[1] += deltay;\n        if (ignore_nodes) {\n            return;\n        }\n        for (var i = 0; i < this._nodes.length; ++i) {\n            var node = this._nodes[i];\n            node.pos[0] += deltax;\n            node.pos[1] += deltay;\n        }\n    };\n\n    LGraphGroup.prototype.recomputeInsideNodes = function() {\n        this._nodes.length = 0;\n        var nodes = this.graph._nodes;\n        var node_bounding = new Float32Array(4);\n\n        for (var i = 0; i < nodes.length; ++i) {\n            var node = nodes[i];\n            node.getBounding(node_bounding);\n            if (!overlapBounding(this._bounding, node_bounding)) {\n                continue;\n            } //out of the visible area\n            this._nodes.push(node);\n        }\n    };\n\n    LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside;\n    LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas;\n\n    //****************************************\n\n    //Scale and Offset\n    function DragAndScale(element, skip_events) {\n        this.offset = new Float32Array([0, 0]);\n        this.scale = 1;\n        this.max_scale = 10;\n        this.min_scale = 0.1;\n        this.onredraw = null;\n        this.enabled = true;\n        this.last_mouse = [0, 0];\n        this.element = null;\n        this.visible_area = new Float32Array(4);\n\n        if (element) {\n            this.element = element;\n            if (!skip_events) {\n                this.bindEvents(element);\n            }\n        }\n    }\n\n    LiteGraph.DragAndScale = DragAndScale;\n\n    DragAndScale.prototype.bindEvents = function(element) {\n        this.last_mouse = new Float32Array(2);\n\n        this._binded_mouse_callback = this.onMouse.bind(this);\n\n\t\tLiteGraph.pointerListenerAdd(element,\"down\", this._binded_mouse_callback);\n\t\tLiteGraph.pointerListenerAdd(element,\"move\", this._binded_mouse_callback);\n\t\tLiteGraph.pointerListenerAdd(element,\"up\", this._binded_mouse_callback);\n\n        element.addEventListener(\n            \"mousewheel\",\n            this._binded_mouse_callback,\n            false\n        );\n        element.addEventListener(\"wheel\", this._binded_mouse_callback, false);\n    };\n\n    DragAndScale.prototype.computeVisibleArea = function( viewport ) {\n        if (!this.element) {\n            this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0;\n            return;\n        }\n        var width = this.element.width;\n        var height = this.element.height;\n        var startx = -this.offset[0];\n        var starty = -this.offset[1];\n\t\tif( viewport )\n\t\t{\n\t\t\tstartx += viewport[0] / this.scale;\n\t\t\tstarty += viewport[1] / this.scale;\n\t\t\twidth = viewport[2];\n\t\t\theight = viewport[3];\n\t\t}\n        var endx = startx + width / this.scale;\n        var endy = starty + height / this.scale;\n        this.visible_area[0] = startx;\n        this.visible_area[1] = starty;\n        this.visible_area[2] = endx - startx;\n        this.visible_area[3] = endy - starty;\n    };\n\n    DragAndScale.prototype.onMouse = function(e) {\n        if (!this.enabled) {\n            return;\n        }\n\n        var canvas = this.element;\n        var rect = canvas.getBoundingClientRect();\n        var x = e.clientX - rect.left;\n        var y = e.clientY - rect.top;\n        e.canvasx = x;\n        e.canvasy = y;\n        e.dragging = this.dragging;\n        \n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\n\t\t//console.log(\"pointerevents: DragAndScale onMouse \"+e.type+\" \"+is_inside);\n\t\t\n        var ignore = false;\n        if (this.onmouse) {\n            ignore = this.onmouse(e);\n        }\n\n        if (e.type == LiteGraph.pointerevents_method+\"down\" && is_inside) {\n            this.dragging = true;\n\t\t\tLiteGraph.pointerListenerRemove(canvas,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(document,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(document,\"up\",this._binded_mouse_callback);\n        } else if (e.type == LiteGraph.pointerevents_method+\"move\") {\n            if (!ignore) {\n                var deltax = x - this.last_mouse[0];\n                var deltay = y - this.last_mouse[1];\n                if (this.dragging) {\n                    this.mouseDrag(deltax, deltay);\n                }\n            }\n        } else if (e.type == LiteGraph.pointerevents_method+\"up\") {\n            this.dragging = false;\n\t\t\tLiteGraph.pointerListenerRemove(document,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerRemove(document,\"up\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(canvas,\"move\",this._binded_mouse_callback);\n        } else if ( is_inside &&\n            (e.type == \"mousewheel\" ||\n            e.type == \"wheel\" ||\n            e.type == \"DOMMouseScroll\")\n        ) {\n            e.eventType = \"mousewheel\";\n            if (e.type == \"wheel\") {\n                e.wheel = -e.deltaY;\n            } else {\n                e.wheel =\n                    e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60;\n            }\n\n            //from stack overflow\n            e.delta = e.wheelDelta\n                ? e.wheelDelta / 40\n                : e.deltaY\n                ? -e.deltaY / 3\n                : 0;\n            this.changeDeltaScale(1.0 + e.delta * 0.05);\n        }\n\n        this.last_mouse[0] = x;\n        this.last_mouse[1] = y;\n\n\t\tif(is_inside)\n\t\t{\n\t        e.preventDefault();\n\t\t    e.stopPropagation();\n\t\t    return false;\n\t\t}\n    };\n\n    DragAndScale.prototype.toCanvasContext = function(ctx) {\n        ctx.scale(this.scale, this.scale);\n        ctx.translate(this.offset[0], this.offset[1]);\n    };\n\n    DragAndScale.prototype.convertOffsetToCanvas = function(pos) {\n        //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]];\n        return [\n            (pos[0] + this.offset[0]) * this.scale,\n            (pos[1] + this.offset[1]) * this.scale\n        ];\n    };\n\n    DragAndScale.prototype.convertCanvasToOffset = function(pos, out) {\n        out = out || [0, 0];\n        out[0] = pos[0] / this.scale - this.offset[0];\n        out[1] = pos[1] / this.scale - this.offset[1];\n        return out;\n    };\n\n    DragAndScale.prototype.mouseDrag = function(x, y) {\n        this.offset[0] += x / this.scale;\n        this.offset[1] += y / this.scale;\n\n        if (this.onredraw) {\n            this.onredraw(this);\n        }\n    };\n\n    DragAndScale.prototype.changeScale = function(value, zooming_center) {\n        if (value < this.min_scale) {\n            value = this.min_scale;\n        } else if (value > this.max_scale) {\n            value = this.max_scale;\n        }\n\n        if (value == this.scale) {\n            return;\n        }\n\n        if (!this.element) {\n            return;\n        }\n\n        var rect = this.element.getBoundingClientRect();\n        if (!rect) {\n            return;\n        }\n\n        zooming_center = zooming_center || [\n            rect.width * 0.5,\n            rect.height * 0.5\n        ];\n        var center = this.convertCanvasToOffset(zooming_center);\n        this.scale = value;\n        if (Math.abs(this.scale - 1) < 0.01) {\n            this.scale = 1;\n        }\n\n        var new_center = this.convertCanvasToOffset(zooming_center);\n        var delta_offset = [\n            new_center[0] - center[0],\n            new_center[1] - center[1]\n        ];\n\n        this.offset[0] += delta_offset[0];\n        this.offset[1] += delta_offset[1];\n\n        if (this.onredraw) {\n            this.onredraw(this);\n        }\n    };\n\n    DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) {\n        this.changeScale(this.scale * value, zooming_center);\n    };\n\n    DragAndScale.prototype.reset = function() {\n        this.scale = 1;\n        this.offset[0] = 0;\n        this.offset[1] = 0;\n    };\n\n    //*********************************************************************************\n    // LGraphCanvas: LGraph renderer CLASS\n    //*********************************************************************************\n\n    /**\n     * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required.\n     * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked\n     *\n     * @class LGraphCanvas\n     * @constructor\n     * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself)\n     * @param {LGraph} graph [optional]\n     * @param {Object} options [optional] { skip_rendering, autoresize, viewport }\n     */\n    function LGraphCanvas(canvas, graph, options) {\n        this.options = options = options || {};\n\n        //if(graph === undefined)\n        //\tthrow (\"No graph assigned\");\n        this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE;\n\n        if (canvas && canvas.constructor === String) {\n            canvas = document.querySelector(canvas);\n        }\n\n        this.ds = new DragAndScale();\n        this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much\n\n        this.title_text_font = \"\" + LiteGraph.NODE_TEXT_SIZE + \"px Arial\";\n        this.inner_text_font =\n            \"normal \" + LiteGraph.NODE_SUBTEXT_SIZE + \"px Arial\";\n        this.node_title_color = LiteGraph.NODE_TITLE_COLOR;\n        this.default_link_color = LiteGraph.LINK_COLOR;\n        this.default_connection_color = {\n            input_off: \"#778\",\n            input_on: \"#7F7\", //\"#BBD\"\n            output_off: \"#778\",\n            output_on: \"#7F7\" //\"#BBD\"\n\t\t};\n        this.default_connection_color_byType = {\n            /*number: \"#7F7\",\n            string: \"#77F\",\n            boolean: \"#F77\",*/\n        }\n        this.default_connection_color_byTypeOff = {\n            /*number: \"#474\",\n            string: \"#447\",\n            boolean: \"#744\",*/\n        };\n\n        this.highquality_render = true;\n        this.use_gradients = false; //set to true to render titlebar with gradients\n        this.editor_alpha = 1; //used for transition\n        this.pause_rendering = false;\n        this.clear_background = true;\n        this.clear_background_color = \"#222\";\n\n\t\tthis.read_only = false; //if set to true users cannot modify the graph\n        this.render_only_selected = true;\n        this.live_mode = false;\n        this.show_info = true;\n        this.allow_dragcanvas = true;\n        this.allow_dragnodes = true;\n        this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc\n        this.multi_select = false; //allow selecting multi nodes without pressing extra keys\n        this.allow_searchbox = true;\n        this.allow_reconnect_links = true; //allows to change a connection with having to redo it again\n\t\tthis.align_to_grid = false; //snap to grid\n\n        this.drag_mode = false;\n        this.dragging_rectangle = null;\n\n        this.filter = null; //allows to filter to only accept some type of nodes in a graph\n\n\t\tthis.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything\n        this.always_render_background = false;\n        this.render_shadows = true;\n        this.render_canvas_border = true;\n        this.render_connections_shadows = false; //too much cpu\n        this.render_connections_border = true;\n        this.render_curved_connections = false;\n        this.render_connection_arrows = false;\n        this.render_collapsed_slots = true;\n        this.render_execution_order = false;\n        this.render_title_colored = true;\n\t\tthis.render_link_tooltip = true;\n\n        this.links_render_mode = LiteGraph.SPLINE_LINK;\n\n        this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle\n        this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle\n\t\tthis.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD\n\n        //to personalize the search box\n        this.onSearchBox = null;\n        this.onSearchBoxSelection = null;\n\n        //callbacks\n        this.onMouse = null;\n        this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform\n        this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform\n        this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs)\n\t\tthis.onDrawLinkTooltip = null; //called when rendering a tooltip\n\t\tthis.onNodeMoved = null; //called after moving a node\n\t\tthis.onSelectionChange = null; //called if the selection changes\n\t\tthis.onConnectingChange = null; //called before any link changes\n\t\tthis.onBeforeChange = null; //called before modifying the graph\n\t\tthis.onAfterChange = null; //called after modifying the graph\n\n        this.connections_width = 3;\n        this.round_radius = 8;\n\n        this.current_node = null;\n        this.node_widget = null; //used for widgets\n\t\tthis.over_link_center = null;\n        this.last_mouse_position = [0, 0];\n        this.visible_area = this.ds.visible_area;\n        this.visible_links = [];\n\n\t\tthis.viewport = options.viewport || null; //to constraint render area to a portion of the canvas\n\n        //link canvas and graph\n        if (graph) {\n            graph.attachCanvas(this);\n        }\n\n        this.setCanvas(canvas,options.skip_events);\n        this.clear();\n\n        if (!options.skip_render) {\n            this.startRendering();\n        }\n\n        this.autoresize = options.autoresize;\n    }\n\n    global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas;\n\n\tLGraphCanvas.DEFAULT_BACKGROUND_IMAGE = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=\";\n\n    LGraphCanvas.link_type_colors = {\n        \"-1\": LiteGraph.EVENT_LINK_COLOR,\n        number: \"#AAA\",\n        node: \"#DCA\"\n    };\n    LGraphCanvas.gradients = {}; //cache of gradients\n\n    /**\n     * clears all the data inside\n     *\n     * @method clear\n     */\n    LGraphCanvas.prototype.clear = function() {\n        this.frame = 0;\n        this.last_draw_time = 0;\n        this.render_time = 0;\n        this.fps = 0;\n\n        //this.scale = 1;\n        //this.offset = [0,0];\n\n        this.dragging_rectangle = null;\n\n        this.selected_nodes = {};\n        this.selected_group = null;\n\n        this.visible_nodes = [];\n        this.node_dragged = null;\n        this.node_over = null;\n        this.node_capturing_input = null;\n        this.connecting_node = null;\n        this.highlighted_links = {};\n\n\t\tthis.dragging_canvas = false;\n\n        this.dirty_canvas = true;\n        this.dirty_bgcanvas = true;\n        this.dirty_area = null;\n\n        this.node_in_panel = null;\n        this.node_widget = null;\n\n        this.last_mouse = [0, 0];\n        this.last_mouseclick = 0;\n\t  \tthis.pointer_is_down = false;\n\t  \tthis.pointer_is_double = false;\n        this.visible_area.set([0, 0, 0, 0]);\n\n        if (this.onClear) {\n            this.onClear();\n        }\n    };\n\n    /**\n     * assigns a graph, you can reassign graphs to the same canvas\n     *\n     * @method setGraph\n     * @param {LGraph} graph\n     */\n    LGraphCanvas.prototype.setGraph = function(graph, skip_clear) {\n        if (this.graph == graph) {\n            return;\n        }\n\n        if (!skip_clear) {\n            this.clear();\n        }\n\n        if (!graph && this.graph) {\n            this.graph.detachCanvas(this);\n            return;\n        }\n\n        graph.attachCanvas(this);\n\n\t\t//remove the graph stack in case a subgraph was open\n\t\tif (this._graph_stack)\n\t\t\tthis._graph_stack = null;\n\n        this.setDirty(true, true);\n    };\n\n    /**\n     * returns the top level graph (in case there are subgraphs open on the canvas)\n     *\n     * @method getTopGraph\n     * @return {LGraph} graph\n     */\n\tLGraphCanvas.prototype.getTopGraph = function()\n\t{\n\t\tif(this._graph_stack.length)\n\t\t\treturn this._graph_stack[0];\n\t\treturn this.graph;\n\t}\n\n    /**\n     * opens a graph contained inside a node in the current graph\n     *\n     * @method openSubgraph\n     * @param {LGraph} graph\n     */\n    LGraphCanvas.prototype.openSubgraph = function(graph) {\n        if (!graph) {\n            throw \"graph cannot be null\";\n        }\n\n        if (this.graph == graph) {\n            throw \"graph cannot be the same\";\n        }\n\n        this.clear();\n\n        if (this.graph) {\n            if (!this._graph_stack) {\n                this._graph_stack = [];\n            }\n            this._graph_stack.push(this.graph);\n        }\n\n        graph.attachCanvas(this);\n\t\tthis.checkPanels();\n        this.setDirty(true, true);\n    };\n\n    /**\n     * closes a subgraph contained inside a node\n     *\n     * @method closeSubgraph\n     * @param {LGraph} assigns a graph\n     */\n    LGraphCanvas.prototype.closeSubgraph = function() {\n        if (!this._graph_stack || this._graph_stack.length == 0) {\n            return;\n        }\n        var subgraph_node = this.graph._subgraph_node;\n        var graph = this._graph_stack.pop();\n        this.selected_nodes = {};\n        this.highlighted_links = {};\n        graph.attachCanvas(this);\n        this.setDirty(true, true);\n        if (subgraph_node) {\n            this.centerOnNode(subgraph_node);\n            this.selectNodes([subgraph_node]);\n        }\n        // when close sub graph back to offset [0, 0] scale 1\n        this.ds.offset = [0, 0]\n        this.ds.scale = 1\n    };\n\n    /**\n     * returns the visually active graph (in case there are more in the stack)\n     * @method getCurrentGraph\n     * @return {LGraph} the active graph\n     */\n    LGraphCanvas.prototype.getCurrentGraph = function() {\n        return this.graph;\n    };\n\n    /**\n     * assigns a canvas\n     *\n     * @method setCanvas\n     * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector)\n     */\n    LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) {\n        var that = this;\n\n        if (canvas) {\n            if (canvas.constructor === String) {\n                canvas = document.getElementById(canvas);\n                if (!canvas) {\n                    throw \"Error creating LiteGraph canvas: Canvas not found\";\n                }\n            }\n        }\n\n        if (canvas === this.canvas) {\n            return;\n        }\n\n        if (!canvas && this.canvas) {\n            //maybe detach events from old_canvas\n            if (!skip_events) {\n                this.unbindEvents();\n            }\n        }\n\n        this.canvas = canvas;\n        this.ds.element = canvas;\n\n        if (!canvas) {\n            return;\n        }\n\n        //this.canvas.tabindex = \"1000\";\n        canvas.className += \" lgraphcanvas\";\n        canvas.data = this;\n        canvas.tabindex = \"1\"; //to allow key events\n\n        //bg canvas: used for non changing stuff\n        this.bgcanvas = null;\n        if (!this.bgcanvas) {\n            this.bgcanvas = document.createElement(\"canvas\");\n            this.bgcanvas.width = this.canvas.width;\n            this.bgcanvas.height = this.canvas.height;\n        }\n\n        if (canvas.getContext == null) {\n            if (canvas.localName != \"canvas\") {\n                throw \"Element supplied for LGraphCanvas must be a <canvas> element, you passed a \" +\n                    canvas.localName;\n            }\n            throw \"This browser doesn't support Canvas\";\n        }\n\n        var ctx = (this.ctx = canvas.getContext(\"2d\"));\n        if (ctx == null) {\n            if (!canvas.webgl_enabled) {\n                console.warn(\n                    \"This canvas seems to be WebGL, enabling WebGL renderer\"\n                );\n            }\n            this.enableWebGL();\n        }\n\n        //input:  (move and up could be unbinded)\n        // why here? this._mousemove_callback = this.processMouseMove.bind(this);\n        // why here? this._mouseup_callback = this.processMouseUp.bind(this);\n\n        if (!skip_events) {\n            this.bindEvents();\n        }\n    };\n\n    //used in some events to capture them\n    LGraphCanvas.prototype._doNothing = function doNothing(e) {\n    \t//console.log(\"pointerevents: _doNothing \"+e.type);\n        e.preventDefault();\n        return false;\n    };\n    LGraphCanvas.prototype._doReturnTrue = function doNothing(e) {\n        e.preventDefault();\n        return true;\n    };\n\n    /**\n     * binds mouse, keyboard, touch and drag events to the canvas\n     * @method bindEvents\n     **/\n    LGraphCanvas.prototype.bindEvents = function() {\n        if (this._events_binded) {\n            console.warn(\"LGraphCanvas: events already binded\");\n            return;\n        }\n\n        //console.log(\"pointerevents: bindEvents\");\n        \n        var canvas = this.canvas;\n\n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document; //hack used when moving canvas between windows\n\n        this._mousedown_callback = this.processMouseDown.bind(this);\n        this._mousewheel_callback = this.processMouseWheel.bind(this);\n        // why mousemove and mouseup were not binded here?\n        this._mousemove_callback = this.processMouseMove.bind(this);\n        this._mouseup_callback = this.processMouseUp.bind(this);\n        \n        //touch events -- TODO IMPLEMENT\n        //this._touch_callback = this.touchHandler.bind(this);\n\n\t\tLiteGraph.pointerListenerAdd(canvas,\"down\", this._mousedown_callback, true); //down do not need to store the binded\n        canvas.addEventListener(\"mousewheel\", this._mousewheel_callback, false);\n\n        LiteGraph.pointerListenerAdd(canvas,\"up\", this._mouseup_callback, true); // CHECK: ??? binded or not\n\t\tLiteGraph.pointerListenerAdd(canvas,\"move\", this._mousemove_callback);\n        \n        canvas.addEventListener(\"contextmenu\", this._doNothing);\n        canvas.addEventListener(\n            \"DOMMouseScroll\",\n            this._mousewheel_callback,\n            false\n        );\n\n        //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents\n        /*if( 'touchstart' in document.documentElement )\n        {\n            canvas.addEventListener(\"touchstart\", this._touch_callback, true);\n            canvas.addEventListener(\"touchmove\", this._touch_callback, true);\n            canvas.addEventListener(\"touchend\", this._touch_callback, true);\n            canvas.addEventListener(\"touchcancel\", this._touch_callback, true);\n        }*/\n\n        //Keyboard ******************\n        this._key_callback = this.processKey.bind(this);\n        canvas.setAttribute(\"tabindex\",1); //otherwise key events are ignored\n        canvas.addEventListener(\"keydown\", this._key_callback, true);\n        document.addEventListener(\"keyup\", this._key_callback, true); //in document, otherwise it doesn't fire keyup\n\n        //Dropping Stuff over nodes ************************************\n        this._ondrop_callback = this.processDrop.bind(this);\n\n        canvas.addEventListener(\"dragover\", this._doNothing, false);\n        canvas.addEventListener(\"dragend\", this._doNothing, false);\n        canvas.addEventListener(\"drop\", this._ondrop_callback, false);\n        canvas.addEventListener(\"dragenter\", this._doReturnTrue, false);\n\n        this._events_binded = true;\n    };\n\n    /**\n     * unbinds mouse events from the canvas\n     * @method unbindEvents\n     **/\n    LGraphCanvas.prototype.unbindEvents = function() {\n        if (!this._events_binded) {\n            console.warn(\"LGraphCanvas: no events binded\");\n            return;\n        }\n\n        //console.log(\"pointerevents: unbindEvents\");\n        \n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document;\n\n\t\tLiteGraph.pointerListenerRemove(this.canvas,\"move\", this._mousedown_callback);\n        LiteGraph.pointerListenerRemove(this.canvas,\"up\", this._mousedown_callback);\n        LiteGraph.pointerListenerRemove(this.canvas,\"down\", this._mousedown_callback);\n        this.canvas.removeEventListener(\n            \"mousewheel\",\n            this._mousewheel_callback\n        );\n        this.canvas.removeEventListener(\n            \"DOMMouseScroll\",\n            this._mousewheel_callback\n        );\n        this.canvas.removeEventListener(\"keydown\", this._key_callback);\n        document.removeEventListener(\"keyup\", this._key_callback);\n        this.canvas.removeEventListener(\"contextmenu\", this._doNothing);\n        this.canvas.removeEventListener(\"drop\", this._ondrop_callback);\n        this.canvas.removeEventListener(\"dragenter\", this._doReturnTrue);\n\n        //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents\n        /*this.canvas.removeEventListener(\"touchstart\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchmove\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchend\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchcancel\", this._touch_callback );*/\n\n        this._mousedown_callback = null;\n        this._mousewheel_callback = null;\n        this._key_callback = null;\n        this._ondrop_callback = null;\n\n        this._events_binded = false;\n    };\n\n    LGraphCanvas.getFileExtension = function(url) {\n        var question = url.indexOf(\"?\");\n        if (question != -1) {\n            url = url.substr(0, question);\n        }\n        var point = url.lastIndexOf(\".\");\n        if (point == -1) {\n            return \"\";\n        }\n        return url.substr(point + 1).toLowerCase();\n    };\n\n    /**\n     * this function allows to render the canvas using WebGL instead of Canvas2D\n     * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL\n     * @method enableWebGL\n     **/\n    LGraphCanvas.prototype.enableWebGL = function() {\n        if (typeof GL === \"undefined\") {\n            throw \"litegl.js must be included to use a WebGL canvas\";\n        }\n        if (typeof enableWebGLCanvas === \"undefined\") {\n            throw \"webglCanvas.js must be included to use this feature\";\n        }\n\n        this.gl = this.ctx = enableWebGLCanvas(this.canvas);\n        this.ctx.webgl = true;\n        this.bgcanvas = this.canvas;\n        this.bgctx = this.gl;\n        this.canvas.webgl_enabled = true;\n\n        /*\n\tGL.create({ canvas: this.bgcanvas });\n\tthis.bgctx = enableWebGLCanvas( this.bgcanvas );\n\twindow.gl = this.gl;\n\t*/\n    };\n\n    /**\n     * marks as dirty the canvas, this way it will be rendered again\n     *\n     * @class LGraphCanvas\n     * @method setDirty\n     * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes)\n     * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires)\n     */\n    LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) {\n        if (fgcanvas) {\n            this.dirty_canvas = true;\n        }\n        if (bgcanvas) {\n            this.dirty_bgcanvas = true;\n        }\n    };\n\n    /**\n     * Used to attach the canvas in a popup\n     *\n     * @method getCanvasWindow\n     * @return {window} returns the window where the canvas is attached (the DOM root node)\n     */\n    LGraphCanvas.prototype.getCanvasWindow = function() {\n        if (!this.canvas) {\n            return window;\n        }\n        var doc = this.canvas.ownerDocument;\n        return doc.defaultView || doc.parentWindow;\n    };\n\n    /**\n     * starts rendering the content of the canvas when needed\n     *\n     * @method startRendering\n     */\n    LGraphCanvas.prototype.startRendering = function() {\n        if (this.is_rendering) {\n            return;\n        } //already rendering\n\n        this.is_rendering = true;\n        renderFrame.call(this);\n\n        function renderFrame() {\n            if (!this.pause_rendering) {\n                this.draw();\n            }\n\n            var window = this.getCanvasWindow();\n            if (this.is_rendering) {\n                window.requestAnimationFrame(renderFrame.bind(this));\n            }\n        }\n    };\n\n    /**\n     * stops rendering the content of the canvas (to save resources)\n     *\n     * @method stopRendering\n     */\n    LGraphCanvas.prototype.stopRendering = function() {\n        this.is_rendering = false;\n        /*\n\tif(this.rendering_timer_id)\n\t{\n\t\tclearInterval(this.rendering_timer_id);\n\t\tthis.rendering_timer_id = null;\n\t}\n\t*/\n    };\n\n    /* LiteGraphCanvas input */\n\n\t//used to block future mouse events (because of im gui)\n\tLGraphCanvas.prototype.blockClick = function()\n\t{\n\t\tthis.block_click = true;\n\t\tthis.last_mouseclick = 0;\n\t}\n\t\n    LGraphCanvas.prototype.processMouseDown = function(e) {\n    \t\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\t\t\n\t\tif (!this.graph) {\n            return;\n        }\n\n        this.adjustMouseEvent(e);\n\n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document;\n        LGraphCanvas.active_canvas = this;\n        var that = this;\n\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\t//console.log(y,this.viewport);\n\t\t//console.log(\"pointerevents: processMouseDown pointerId:\"+e.pointerId+\" which:\"+e.which+\" isPrimary:\"+e.isPrimary+\" :: x y \"+x+\" \"+y);\n\n\t\tthis.ds.viewport = this.viewport;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\n        //move mouse move event to the window in case it drags outside of the canvas\n\t\tif(!this.options.skip_events)\n\t\t{\n\t\t\tLiteGraph.pointerListenerRemove(this.canvas,\"move\", this._mousemove_callback);\n\t\t\tLiteGraph.pointerListenerAdd(ref_window.document,\"move\", this._mousemove_callback,true); //catch for the entire window\n\t\t\tLiteGraph.pointerListenerAdd(ref_window.document,\"up\", this._mouseup_callback,true);\n\t\t}\n\n\t\tif(!is_inside){\n\t\t\treturn;\n\t\t}\n\n        var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 );\n        var skip_dragging = false;\n        var skip_action = false;\n        var now = LiteGraph.getTime();\n\t\tvar is_primary = (e.isPrimary === undefined || !e.isPrimary);\n        var is_double_click = (now - this.last_mouseclick < 300) && is_primary;\n\t\tthis.mouse[0] = e.clientX;\n\t\tthis.mouse[1] = e.clientY;\n        this.graph_mouse[0] = e.canvasX;\n        this.graph_mouse[1] = e.canvasY;\n\t\tthis.last_click_position = [this.mouse[0],this.mouse[1]];\n\t  \t\n\t  \tif (this.pointer_is_down && is_primary ){\n\t\t  this.pointer_is_double = true;\n\t\t  //console.log(\"pointerevents: pointer_is_double start\");\n\t\t}else{\n\t\t  this.pointer_is_double = false;\n\t\t}\n\t  \tthis.pointer_is_down = true;\n\t  \n\t  \t\n        this.canvas.focus();\n\n        LiteGraph.closeAllContextMenus(ref_window);\n\n        if (this.onMouse)\n\t\t{\n            if (this.onMouse(e) == true)\n                return;\n        }\n\n\t\t//left button mouse / single finger\n        if (e.which == 1 && !this.pointer_is_double)\n\t\t{\n            if (e.ctrlKey)\n\t\t\t{\n                this.dragging_rectangle = new Float32Array(4);\n                this.dragging_rectangle[0] = e.canvasX;\n                this.dragging_rectangle[1] = e.canvasY;\n                this.dragging_rectangle[2] = 1;\n                this.dragging_rectangle[3] = 1;\n                skip_action = true;\n            }\n\n            // clone node ALT dragging\n            if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && node && this.allow_interaction && !skip_action && !this.read_only)\n            {\n                if (cloned = node.clone()){\n                    cloned.pos[0] += 5;\n                    cloned.pos[1] += 5;\n                    this.graph.add(cloned,false,{doCalcSize: false});\n                    node = cloned;\n                    skip_action = true;\n                    if (!block_drag_node) {\n                        if (this.allow_dragnodes) {\n\t\t\t\t\t\t\tthis.graph.beforeChange();\n                            this.node_dragged = node;\n                        }\n                        if (!this.selected_nodes[node.id]) {\n                            this.processNodeSelected(node, e);\n                        }\n                    }\n                }\n            }\n            \n            var clicking_canvas_bg = false;\n\n            //when clicked on top of a node\n            //and it is not interactive\n            if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) {\n                if (!this.live_mode && !node.flags.pinned) {\n                    this.bringToFront(node);\n                } //if it wasn't selected?\n\n                //not dragging mouse to connect two slots\n                if ( this.allow_interaction && !this.connecting_node && !node.flags.collapsed && !this.live_mode ) {\n                    //Search for corner for resize\n                    if ( !skip_action &&\n                        node.resizable !== false &&\n                        isInsideRectangle( e.canvasX,\n                            e.canvasY,\n                            node.pos[0] + node.size[0] - 5,\n                            node.pos[1] + node.size[1] - 5,\n                            10,\n                            10\n                        )\n                    ) {\n\t\t\t\t\t\tthis.graph.beforeChange();\n                        this.resizing_node = node;\n                        this.canvas.style.cursor = \"se-resize\";\n                        skip_action = true;\n                    } else {\n                        //search for outputs\n                        if (node.outputs) {\n                            for ( var i = 0, l = node.outputs.length; i < l; ++i ) {\n                                var output = node.outputs[i];\n                                var link_pos = node.getConnectionPos(false, i);\n                                if (\n                                    isInsideRectangle(\n                                        e.canvasX,\n                                        e.canvasY,\n                                        link_pos[0] - 15,\n                                        link_pos[1] - 10,\n                                        30,\n                                        20\n                                    )\n                                ) {\n                                    this.connecting_node = node;\n                                    this.connecting_output = output;\n                                    this.connecting_output.slot_index = i;\n                                    this.connecting_pos = node.getConnectionPos( false, i );\n                                    this.connecting_slot = i;\n\n                                    if (LiteGraph.shift_click_do_break_link_from){\n                                        if (e.shiftKey) {\n                                            node.disconnectOutput(i);\n                                        }\n                                    }\n\n                                    if (is_double_click) {\n                                        if (node.onOutputDblClick) {\n                                            node.onOutputDblClick(i, e);\n                                        }\n                                    } else {\n                                        if (node.onOutputClick) {\n                                            node.onOutputClick(i, e);\n                                        }\n                                    }\n\n                                    skip_action = true;\n                                    break;\n                                }\n                            }\n                        }\n\n                        //search for inputs\n                        if (node.inputs) {\n                            for ( var i = 0, l = node.inputs.length; i < l; ++i ) {\n                                var input = node.inputs[i];\n                                var link_pos = node.getConnectionPos(true, i);\n                                if (\n                                    isInsideRectangle(\n                                        e.canvasX,\n                                        e.canvasY,\n                                        link_pos[0] - 15,\n                                        link_pos[1] - 10,\n                                        30,\n                                        20\n                                    )\n                                ) {\n                                    if (is_double_click) {\n                                        if (node.onInputDblClick) {\n                                            node.onInputDblClick(i, e);\n                                        }\n                                    } else {\n                                        if (node.onInputClick) {\n                                            node.onInputClick(i, e);\n                                        }\n                                    }\n\n                                    if (input.link !== null) {\n                                        var link_info = this.graph.links[\n                                            input.link\n                                        ]; //before disconnecting\n                                        if (LiteGraph.click_do_break_link_to){\n                                            node.disconnectInput(i);\n                                            this.dirty_bgcanvas = true;\n                                            skip_action = true;\n                                        }else{\n                                            // do same action as has not node ?\n                                        }\n\n                                        if (\n                                            this.allow_reconnect_links ||\n\t\t\t\t\t\t\t\t\t\t\t//this.move_destination_link_without_shift ||\n                                            e.shiftKey\n                                        ) {\n                                            if (!LiteGraph.click_do_break_link_to){\n                                                node.disconnectInput(i);\n                                            }\n                                            this.connecting_node = this.graph._nodes_by_id[\n                                                link_info.origin_id\n                                            ];\n                                            this.connecting_slot =\n                                                link_info.origin_slot;\n                                            this.connecting_output = this.connecting_node.outputs[\n                                                this.connecting_slot\n                                            ];\n                                            this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot );\n                                            \n                                            this.dirty_bgcanvas = true;\n                                            skip_action = true;\n                                        }\n\n                                        \n                                    }else{\n                                        // has not node\n                                    }\n                                    \n                                    if (!skip_action){\n                                        // connect from in to out, from to to from\n                                        this.connecting_node = node;\n                                        this.connecting_input = input;\n                                        this.connecting_input.slot_index = i;\n                                        this.connecting_pos = node.getConnectionPos( true, i );\n                                        this.connecting_slot = i;\n                                        \n                                        this.dirty_bgcanvas = true;\n                                        skip_action = true;\n                                    }\n                                }\n                            }\n                        }\n                    } //not resizing\n                }\n\n                //it wasn't clicked on the links boxes\n                if (!skip_action) {\n                    var block_drag_node = false;\n\t\t\t\t\tvar pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]];\n\n                    //widgets\n                    var widget = this.processNodeWidgets( node, this.graph_mouse, e );\n                    if (widget) {\n                        block_drag_node = true;\n                        this.node_widget = [node, widget];\n                    }\n\n                    //double clicking\n                    if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) {\n                        //double click node\n                        if (node.onDblClick) {\n                            node.onDblClick( e, pos, this );\n                        }\n                        this.processNodeDblClicked(node);\n                        block_drag_node = true;\n                    }\n\n                    //if do not capture mouse\n                    if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) {\n                        block_drag_node = true;\n                    } else {\n\t\t\t\t\t\t//open subgraph button\n\t\t\t\t\t\tif(node.subgraph && !node.skip_subgraph_button)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) {\n\t\t\t\t\t\t\t\tvar that = this;\n\t\t\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\t\t\tthat.openSubgraph(node.subgraph);\n\t\t\t\t\t\t\t\t}, 10);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (this.live_mode) {\n\t\t\t\t\t\t\tclicking_canvas_bg = true;\n\t                        block_drag_node = true;\n\t\t\t\t\t\t}\n                    }\n\n                    if (!block_drag_node) {\n                        if (this.allow_dragnodes) {\n\t\t\t\t\t\t\tthis.graph.beforeChange();\n                            this.node_dragged = node;\n                        }\n                        this.processNodeSelected(node, e);\n                    } else { // double-click\n                        /**\n                         * Don't call the function if the block is already selected.\n                         * Otherwise, it could cause the block to be unselected while its panel is open.\n                         */\n                        if (!node.is_selected) this.processNodeSelected(node, e);\n                    }\n\n                    this.dirty_canvas = true;\n                }\n            } //clicked outside of nodes\n            else {\n\t\t\t\tif (!skip_action){\n\t\t\t\t\t//search for link connector\n\t\t\t\t\tif(!this.read_only) {\n\t\t\t\t\t\tfor (var i = 0; i < this.visible_links.length; ++i) {\n\t\t\t\t\t\t\tvar link = this.visible_links[i];\n\t\t\t\t\t\t\tvar center = link._pos;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!center ||\n\t\t\t\t\t\t\t\te.canvasX < center[0] - 4 ||\n\t\t\t\t\t\t\t\te.canvasX > center[0] + 4 ||\n\t\t\t\t\t\t\t\te.canvasY < center[1] - 4 ||\n\t\t\t\t\t\t\t\te.canvasY > center[1] + 4\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t//link clicked\n\t\t\t\t\t\t\tthis.showLinkMenu(link, e);\n\t\t\t\t\t\t\tthis.over_link_center = null; //clear tooltip\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY );\n\t\t\t\t\tthis.selected_group_resizing = false;\n\t\t\t\t\tif (this.selected_group && !this.read_only ) {\n\t\t\t\t\t\tif (e.ctrlKey) {\n\t\t\t\t\t\t\tthis.dragging_rectangle = null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] );\n\t\t\t\t\t\tif (dist * this.ds.scale < 10) {\n\t\t\t\t\t\t\tthis.selected_group_resizing = true;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.selected_group.recomputeInsideNodes();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (is_double_click && !this.read_only && this.allow_searchbox) {\n\t\t\t\t\t\tthis.showSearchBox(e);\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t}\n\n\t\t\t\t\tclicking_canvas_bg = true;\n\t\t\t\t}\n            }\n\n            if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) {\n            \t//console.log(\"pointerevents: dragging_canvas start\");\n            \tthis.dragging_canvas = true;\n            }\n            \n        } else if (e.which == 2) {\n            //middle button\n        \t\n\t\t\tif (LiteGraph.middle_click_slot_add_default_node){\n\t\t\t\tif (node && this.allow_interaction && !skip_action && !this.read_only){\n\t\t\t\t\t//not dragging mouse to connect two slots\n\t\t\t\t\tif (\n\t\t\t\t\t\t!this.connecting_node &&\n\t\t\t\t\t\t!node.flags.collapsed &&\n\t\t\t\t\t\t!this.live_mode\n\t\t\t\t\t) {\n\t\t\t\t\t\tvar mClikSlot = false;\n\t\t\t\t\t\tvar mClikSlot_index = false;\n\t\t\t\t\t\tvar mClikSlot_isOut = false;\n\t\t\t\t\t\t//search for outputs\n\t\t\t\t\t\tif (node.outputs) {\n\t\t\t\t\t\t\tfor ( var i = 0, l = node.outputs.length; i < l; ++i ) {\n\t\t\t\t\t\t\t\tvar output = node.outputs[i];\n\t\t\t\t\t\t\t\tvar link_pos = node.getConnectionPos(false, i);\n\t\t\t\t\t\t\t\tif (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) {\n\t\t\t\t\t\t\t\t\tmClikSlot = output;\n\t\t\t\t\t\t\t\t\tmClikSlot_index = i;\n\t\t\t\t\t\t\t\t\tmClikSlot_isOut = true;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t//search for inputs\n\t\t\t\t\t\tif (node.inputs) {\n\t\t\t\t\t\t\tfor ( var i = 0, l = node.inputs.length; i < l; ++i ) {\n\t\t\t\t\t\t\t\tvar input = node.inputs[i];\n\t\t\t\t\t\t\t\tvar link_pos = node.getConnectionPos(true, i);\n\t\t\t\t\t\t\t\tif (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) {\n\t\t\t\t\t\t\t\t\tmClikSlot = input;\n\t\t\t\t\t\t\t\t\tmClikSlot_index = i;\n\t\t\t\t\t\t\t\t\tmClikSlot_isOut = false;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//console.log(\"middleClickSlots? \"+mClikSlot+\" & \"+(mClikSlot_index!==false));\n\t\t\t\t\t\tif (mClikSlot && mClikSlot_index!==false){\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tvar alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length)));\n\t\t\t\t\t\t\tvar node_bounding = node.getBounding();\n\t\t\t\t\t\t\t// estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes\n\t\t\t\t\t\t\tvar posRef = [\t(!mClikSlot_isOut?node_bounding[0]:node_bounding[0]+node_bounding[2])// + node_bounding[0]/this.canvas.width*150\n\t\t\t\t\t\t\t\t\t\t\t,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical \"derive\"\n\t\t\t\t\t\t\t\t\t\t  ];\n\t\t\t\t\t\t\tvar nodeCreated = this.createDefaultNodeForSlot({   \tnodeFrom: !mClikSlot_isOut?null:node\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotFrom: !mClikSlot_isOut?null:mClikSlot_index\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeTo: !mClikSlot_isOut?node:null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotTo: !mClikSlot_isOut?mClikSlot_index:null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,position: posRef //,e: e\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: \"AUTO\" //nodeNewType\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,posAdd:[!mClikSlot_isOut?-30:30, -alphaPosY*130] //-alphaPosY*30]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,posSizeFix:[!mClikSlot_isOut?-1:0, 0] //-alphaPosY*2*/\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (!skip_action && this.allow_dragcanvas) {\n            \t//console.log(\"pointerevents: dragging_canvas start from middle button\");\n            \tthis.dragging_canvas = true;\n            }\n\n        \t\n        } else if (e.which == 3 || this.pointer_is_double) {\n\t\t\t\n            //right button\n\t\t\tif (this.allow_interaction && !skip_action && !this.read_only){\n\t\t\t\t\n\t\t\t\t// is it hover a node ?\n\t\t\t\tif (node){\n\t\t\t\t\tif(Object.keys(this.selected_nodes).length\n\t\t\t\t\t   && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey)\n\t\t\t\t\t){\n\t\t\t\t\t\t// is multiselected or using shift to include the now node\n\t\t\t\t\t\tif (!this.selected_nodes[node.id]) this.selectNodes([node],true); // add this if not present\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// update selection\n\t\t\t\t\t\tthis.selectNodes([node]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// show menu on this node\n\t\t\t\tthis.processContextMenu(node, e);\n\t\t\t}\n\t\t\t\n        }\n\n        //TODO\n        //if(this.node_selected != prev_selected)\n        //\tthis.onNodeSelectionChange(this.node_selected);\n\n        this.last_mouse[0] = e.clientX;\n        this.last_mouse[1] = e.clientY;\n        this.last_mouseclick = LiteGraph.getTime();\n        this.last_mouse_dragging = true;\n\n        /*\n\tif( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)\n\t\tthis.draw();\n\t*/\n\n        this.graph.change();\n\n        //this is to ensure to defocus(blur) if a text input element is on focus\n        if (\n            !ref_window.document.activeElement ||\n            (ref_window.document.activeElement.nodeName.toLowerCase() !=\n                \"input\" &&\n                ref_window.document.activeElement.nodeName.toLowerCase() !=\n                    \"textarea\")\n        ) {\n            e.preventDefault();\n        }\n        e.stopPropagation();\n\n        if (this.onMouseDown) {\n            this.onMouseDown(e);\n        }\n\n        return false;\n    };\n\n    /**\n     * Called when a mouse move event has to be processed\n     * @method processMouseMove\n     **/\n    LGraphCanvas.prototype.processMouseMove = function(e) {\n        if (this.autoresize) {\n            this.resize();\n        }\n\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\n        if (!this.graph) {\n            return;\n        }\n\n        LGraphCanvas.active_canvas = this;\n        this.adjustMouseEvent(e);\n        var mouse = [e.clientX, e.clientY];\n\t\tthis.mouse[0] = mouse[0];\n\t\tthis.mouse[1] = mouse[1];\n        var delta = [\n            mouse[0] - this.last_mouse[0],\n            mouse[1] - this.last_mouse[1]\n        ];\n        this.last_mouse = mouse;\n        this.graph_mouse[0] = e.canvasX;\n        this.graph_mouse[1] = e.canvasY;\n\n        //console.log(\"pointerevents: processMouseMove \"+e.pointerId+\" \"+e.isPrimary);\n        \n\t\tif(this.block_click)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseMove block_click\");\n\t\t\te.preventDefault();\n\t\t\treturn false;\n\t\t}\n\n        e.dragging = this.last_mouse_dragging;\n\n        if (this.node_widget) {\n            this.processNodeWidgets(\n                this.node_widget[0],\n                this.graph_mouse,\n                e,\n                this.node_widget[1]\n            );\n            this.dirty_canvas = true;\n        }\n\n        //get node over\n        var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes);\n\n        if (this.dragging_rectangle)\n\t\t{\n            this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0];\n            this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1];\n            this.dirty_canvas = true;\n        } \n\t\telse if (this.selected_group && !this.read_only)\n\t\t{\n            //moving/resizing a group\n            if (this.selected_group_resizing) {\n                this.selected_group.size = [\n                    e.canvasX - this.selected_group.pos[0],\n                    e.canvasY - this.selected_group.pos[1]\n                ];\n            } else {\n                var deltax = delta[0] / this.ds.scale;\n                var deltay = delta[1] / this.ds.scale;\n                this.selected_group.move(deltax, deltay, e.ctrlKey);\n                if (this.selected_group._nodes.length) {\n                    this.dirty_canvas = true;\n                }\n            }\n            this.dirty_bgcanvas = true;\n        } else if (this.dragging_canvas) {\n        \t////console.log(\"pointerevents: processMouseMove is dragging_canvas\");\n            this.ds.offset[0] += delta[0] / this.ds.scale;\n            this.ds.offset[1] += delta[1] / this.ds.scale;\n            this.dirty_canvas = true;\n            this.dirty_bgcanvas = true;\n        } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) {\n            if (this.connecting_node) {\n                this.dirty_canvas = true;\n            }\n\n            //remove mouseover flag\n            for (var i = 0, l = this.graph._nodes.length; i < l; ++i) {\n                if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) {\n                    //mouse leave\n                    this.graph._nodes[i].mouseOver = false;\n                    if (this.node_over && this.node_over.onMouseLeave) {\n                        this.node_over.onMouseLeave(e);\n                    }\n                    this.node_over = null;\n                    this.dirty_canvas = true;\n                }\n            }\n\n            //mouse over a node\n            if (node) {\n\n\t\t\t\tif(node.redraw_on_mouse)\n                    this.dirty_canvas = true;\n\n                //this.canvas.style.cursor = \"move\";\n                if (!node.mouseOver) {\n                    //mouse enter\n                    node.mouseOver = true;\n                    this.node_over = node;\n                    this.dirty_canvas = true;\n\n                    if (node.onMouseEnter) {\n                        node.onMouseEnter(e);\n                    }\n                }\n\n                //in case the node wants to do something\n                if (node.onMouseMove) {\n                    node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this );\n                }\n\n                //if dragging a link\n                if (this.connecting_node) {\n                    \n                    if (this.connecting_output){\n                        \n                        var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput\n\n                        //on top of input\n                        if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {\n                            //mouse on top of the corner box, don't know what to do\n                        } else {\n                            //check if I have a slot below de mouse\n                            var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos );\n                            if (slot != -1 && node.inputs[slot]) {\n                                var slot_type = node.inputs[slot].type;\n                                if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) {\n                                    this._highlight_input = pos;\n\t\t\t\t\t\t\t\t\tthis._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS\n                                }\n                            } else {\n                                this._highlight_input = null;\n\t\t\t\t\t\t\t\tthis._highlight_input_slot = null;  // XXX CHECK THIS\n                            }\n                        }\n                        \n                    }else if(this.connecting_input){\n                        \n                        var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput\n\n                        //on top of output\n                        if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {\n                            //mouse on top of the corner box, don't know what to do\n                        } else {\n                            //check if I have a slot below de mouse\n                            var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos );\n                            if (slot != -1 && node.outputs[slot]) {\n                                var slot_type = node.outputs[slot].type;\n                                if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) {\n                                    this._highlight_output = pos;\n                                }\n                            } else {\n                                this._highlight_output = null;\n                            }\n                        }\n                    }\n                }\n\n                //Search for corner\n                if (this.canvas) {\n                    if (\n                        isInsideRectangle(\n                            e.canvasX,\n                            e.canvasY,\n                            node.pos[0] + node.size[0] - 5,\n                            node.pos[1] + node.size[1] - 5,\n                            5,\n                            5\n                        )\n                    ) {\n                        this.canvas.style.cursor = \"se-resize\";\n                    } else {\n                        this.canvas.style.cursor = \"crosshair\";\n                    }\n                }\n            } else { //not over a node\n\n                //search for link connector\n\t\t\t\tvar over_link = null;\n\t\t\t\tfor (var i = 0; i < this.visible_links.length; ++i) {\n\t\t\t\t\tvar link = this.visible_links[i];\n\t\t\t\t\tvar center = link._pos;\n\t\t\t\t\tif (\n\t\t\t\t\t\t!center ||\n\t\t\t\t\t\te.canvasX < center[0] - 4 ||\n\t\t\t\t\t\te.canvasX > center[0] + 4 ||\n\t\t\t\t\t\te.canvasY < center[1] - 4 ||\n\t\t\t\t\t\te.canvasY > center[1] + 4\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tover_link = link;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif( over_link != this.over_link_center )\n\t\t\t\t{\n\t\t\t\t\tthis.over_link_center = over_link;\n\t                this.dirty_canvas = true;\n\t\t\t\t}\n\n\t\t\t\tif (this.canvas) {\n\t                this.canvas.style.cursor = \"\";\n\t\t\t\t}\n\t\t\t} //end\n\n\t\t\t//send event to node if capturing input (used with widgets that allow drag outside of the area of the node)\n            if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) {\n                this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this);\n            }\n\n\t\t\t//node being dragged\n            if (this.node_dragged && !this.live_mode) {\n\t\t\t\t//console.log(\"draggin!\",this.selected_nodes);\n                for (var i in this.selected_nodes) {\n                    var n = this.selected_nodes[i];\n                    n.pos[0] += delta[0] / this.ds.scale;\n                    n.pos[1] += delta[1] / this.ds.scale;\n                    if (!n.is_selected) this.processNodeSelected(n, e); /*\n                     * Don't call the function if the block is already selected.\n                     * Otherwise, it could cause the block to be unselected while dragging.\n                     */\n                }\n\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n            }\n\n            if (this.resizing_node && !this.live_mode) {\n                //convert mouse to node space\n\t\t\t\tvar desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ];\n\t\t\t\tvar min_size = this.resizing_node.computeSize();\n\t\t\t\tdesired_size[0] = Math.max( min_size[0], desired_size[0] );\n\t\t\t\tdesired_size[1] = Math.max( min_size[1], desired_size[1] );\n\t\t\t\tthis.resizing_node.setSize( desired_size );\n\n                this.canvas.style.cursor = \"se-resize\";\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n            }\n        }\n\n        e.preventDefault();\n        return false;\n    };\n\n    /**\n     * Called when a mouse up event has to be processed\n     * @method processMouseUp\n     **/\n    LGraphCanvas.prototype.processMouseUp = function(e) {\n\n\t\tvar is_primary = ( e.isPrimary === undefined || e.isPrimary );\n\n    \t//early exit for extra pointer\n    \tif(!is_primary){\n    \t\t/*e.stopPropagation();\n        \te.preventDefault();*/\n    \t\t//console.log(\"pointerevents: processMouseUp pointerN_stop \"+e.pointerId+\" \"+e.isPrimary);\n    \t\treturn false;\n    \t}\n    \t\n    \t//console.log(\"pointerevents: processMouseUp \"+e.pointerId+\" \"+e.isPrimary+\" :: \"+e.clientX+\" \"+e.clientY);\n    \t\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\n        if (!this.graph)\n            return;\n\n        var window = this.getCanvasWindow();\n        var document = window.document;\n        LGraphCanvas.active_canvas = this;\n\n        //restore the mousemove event back to the canvas\n\t\tif(!this.options.skip_events)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseUp adjustEventListener\");\n\t\t\tLiteGraph.pointerListenerRemove(document,\"move\", this._mousemove_callback,true);\n\t\t\tLiteGraph.pointerListenerAdd(this.canvas,\"move\", this._mousemove_callback,true);\n\t\t\tLiteGraph.pointerListenerRemove(document,\"up\", this._mouseup_callback,true);\n\t\t}\n\n        this.adjustMouseEvent(e);\n        var now = LiteGraph.getTime();\n        e.click_time = now - this.last_mouseclick;\n        this.last_mouse_dragging = false;\n\t\tthis.last_click_position = null;\n\n\t\tif(this.block_click)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseUp block_clicks\");\n\t\t\tthis.block_click = false; //used to avoid sending twice a click in a immediate button\n\t\t}\n\n\t\t//console.log(\"pointerevents: processMouseUp which: \"+e.which);\n\t\t\n        if (e.which == 1) {\n\n\t\t\tif( this.node_widget )\n\t\t\t{\n\t\t\t\tthis.processNodeWidgets( this.node_widget[0], this.graph_mouse, e );\n\t\t\t}\n\n            //left button\n            this.node_widget = null;\n\n            if (this.selected_group) {\n                var diffx =\n                    this.selected_group.pos[0] -\n                    Math.round(this.selected_group.pos[0]);\n                var diffy =\n                    this.selected_group.pos[1] -\n                    Math.round(this.selected_group.pos[1]);\n                this.selected_group.move(diffx, diffy, e.ctrlKey);\n                this.selected_group.pos[0] = Math.round(\n                    this.selected_group.pos[0]\n                );\n                this.selected_group.pos[1] = Math.round(\n                    this.selected_group.pos[1]\n                );\n                if (this.selected_group._nodes.length) {\n                    this.dirty_canvas = true;\n                }\n                this.selected_group = null;\n            }\n            this.selected_group_resizing = false;\n\n\t\t\tvar node = this.graph.getNodeOnPos(\n\t\t\t\t\t\t\te.canvasX,\n\t\t\t\t\t\t\te.canvasY,\n\t\t\t\t\t\t\tthis.visible_nodes\n\t\t\t\t\t\t);\n\t\t\t\n            if (this.dragging_rectangle) {\n                if (this.graph) {\n                    var nodes = this.graph._nodes;\n                    var node_bounding = new Float32Array(4);\n                    \n                    //compute bounding and flip if left to right\n                    var w = Math.abs(this.dragging_rectangle[2]);\n                    var h = Math.abs(this.dragging_rectangle[3]);\n                    var startx =\n                        this.dragging_rectangle[2] < 0\n                            ? this.dragging_rectangle[0] - w\n                            : this.dragging_rectangle[0];\n                    var starty =\n                        this.dragging_rectangle[3] < 0\n                            ? this.dragging_rectangle[1] - h\n                            : this.dragging_rectangle[1];\n                    this.dragging_rectangle[0] = startx;\n                    this.dragging_rectangle[1] = starty;\n                    this.dragging_rectangle[2] = w;\n                    this.dragging_rectangle[3] = h;\n\n\t\t\t\t\t// test dragging rect size, if minimun simulate a click\n\t\t\t\t\tif (!node || (w > 10 && h > 10 )){\n\t\t\t\t\t\t//test against all nodes (not visible because the rectangle maybe start outside\n\t\t\t\t\t\tvar to_select = [];\n\t\t\t\t\t\tfor (var i = 0; i < nodes.length; ++i) {\n\t\t\t\t\t\t\tvar nodeX = nodes[i];\n\t\t\t\t\t\t\tnodeX.getBounding(node_bounding);\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!overlapBounding(\n\t\t\t\t\t\t\t\t\tthis.dragging_rectangle,\n\t\t\t\t\t\t\t\t\tnode_bounding\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t} //out of the visible area\n\t\t\t\t\t\t\tto_select.push(nodeX);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (to_select.length) {\n\t\t\t\t\t\t\tthis.selectNodes(to_select,e.shiftKey); // add to selection with shift\n\t\t\t\t\t\t}\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// will select of update selection\n\t\t\t\t\t\tthis.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey\n\t\t\t\t\t}\n\t\t\t\t\t\n                }\n                this.dragging_rectangle = null;\n            } else if (this.connecting_node) {\n                //dragging a connection\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n\n                var connInOrOut = this.connecting_output || this.connecting_input;\n                var connType = connInOrOut.type;\n                \n                //node below mouse\n                if (node) {\n                    \n                    /* no need to condition on event type.. just another type\n                    if (\n                        connType == LiteGraph.EVENT &&\n                        this.isOverNodeBox(node, e.canvasX, e.canvasY)\n                    ) {\n                        \n                        this.connecting_node.connect(\n                            this.connecting_slot,\n                            node,\n                            LiteGraph.EVENT\n                        );\n                        \n                    } else {*/\n                        \n                        //slot below mouse? connect\n                        \n                        if (this.connecting_output){\n                            \n                            var slot = this.isOverNodeInput(\n                                node,\n                                e.canvasX,\n                                e.canvasY\n                            );\n                            if (slot != -1) {\n                                this.connecting_node.connect(this.connecting_slot, node, slot);\n                            } else {\n                                //not on top of an input\n                                // look for a good slot\n                                this.connecting_node.connectByType(this.connecting_slot,node,connType);\n                            }\n                            \n                        }else if (this.connecting_input){\n                            \n                            var slot = this.isOverNodeOutput(\n                                node,\n                                e.canvasX,\n                                e.canvasY\n                            );\n\n                            if (slot != -1) {\n                                node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like\n                            } else {\n                                //not on top of an input\n                                // look for a good slot\n                                this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType);\n                            }\n                            \n                        }\n                        \n                        \n                    //}\n                    \n                }else{\n                    \n                    // add menu when releasing link in empty space\n                \tif (LiteGraph.release_link_on_empty_shows_menu){\n\t                    if (e.shiftKey && this.allow_searchbox){\n\t                        if(this.connecting_output){\n\t                            this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type});\n\t                        }else if(this.connecting_input){\n\t                            this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type});\n\t                        }\n\t                    }else{\n\t                        if(this.connecting_output){\n\t                            this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e});\n\t                        }else if(this.connecting_input){\n\t                            this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e});\n\t                        }\n\t                    }\n                \t}\n                }\n\n                this.connecting_output = null;\n                this.connecting_input = null;\n                this.connecting_pos = null;\n                this.connecting_node = null;\n                this.connecting_slot = -1;\n            } //not dragging connection\n            else if (this.resizing_node) {\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n\t\t\t\tthis.graph.afterChange(this.resizing_node);\n                this.resizing_node = null;\n            } else if (this.node_dragged) {\n                //node being dragged?\n                var node = this.node_dragged;\n                if (\n                    node &&\n                    e.click_time < 300 &&\n                    isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT )\n                ) {\n                    node.collapse();\n                }\n\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n                this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]);\n                this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]);\n                if (this.graph.config.align_to_grid || this.align_to_grid ) {\n                    this.node_dragged.alignToGrid();\n                }\n\t\t\t\tif( this.onNodeMoved )\n\t\t\t\t\tthis.onNodeMoved( this.node_dragged );\n\t\t\t\tthis.graph.afterChange(this.node_dragged);\n                this.node_dragged = null;\n            } //no node being dragged\n            else {\n                //get node over\n                var node = this.graph.getNodeOnPos(\n                    e.canvasX,\n                    e.canvasY,\n                    this.visible_nodes\n                );\n\n                if (!node && e.click_time < 300) {\n                    this.deselectAllNodes();\n                }\n\n                this.dirty_canvas = true;\n                this.dragging_canvas = false;\n\n                if (this.node_over && this.node_over.onMouseUp) {\n                    this.node_over.onMouseUp( e, [ e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1] ], this );\n                }\n                if (\n                    this.node_capturing_input &&\n                    this.node_capturing_input.onMouseUp\n                ) {\n                    this.node_capturing_input.onMouseUp(e, [\n                        e.canvasX - this.node_capturing_input.pos[0],\n                        e.canvasY - this.node_capturing_input.pos[1]\n                    ]);\n                }\n            }\n        } else if (e.which == 2) {\n            //middle button\n            //trace(\"middle\");\n            this.dirty_canvas = true;\n            this.dragging_canvas = false;\n        } else if (e.which == 3) {\n            //right button\n            //trace(\"right\");\n            this.dirty_canvas = true;\n            this.dragging_canvas = false;\n        }\n\n        /*\n\t\tif((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)\n\t\t\tthis.draw();\n\t\t*/\n\n\t  \tif (is_primary)\n\t\t{\n\t\t\tthis.pointer_is_down = false;\n\t\t\tthis.pointer_is_double = false;\n\t\t}\n\t  \n        this.graph.change();\n\n        //console.log(\"pointerevents: processMouseUp stopPropagation\");\n        e.stopPropagation();\n        e.preventDefault();\n        return false;\n    };\n\n    /**\n     * Called when a mouse wheel event has to be processed\n     * @method processMouseWheel\n     **/\n    LGraphCanvas.prototype.processMouseWheel = function(e) {\n        if (!this.graph || !this.allow_dragcanvas) {\n            return;\n        }\n\n        var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60;\n\n        this.adjustMouseEvent(e);\n\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\t\tif(!is_inside)\n\t\t\treturn;\n\n        var scale = this.ds.scale;\n\n        if (delta > 0) {\n            scale *= 1.1;\n        } else if (delta < 0) {\n            scale *= 1 / 1.1;\n        }\n\n        //this.setZoom( scale, [ e.clientX, e.clientY ] );\n        this.ds.changeScale(scale, [e.clientX, e.clientY]);\n\n        this.graph.change();\n\n        e.preventDefault();\n        return false; // prevent default\n    };\n\n    /**\n     * returns true if a position (in graph space) is on top of a node little corner box\n     * @method isOverNodeBox\n     **/\n    LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) {\n        var title_height = LiteGraph.NODE_TITLE_HEIGHT;\n        if (\n            isInsideRectangle(\n                canvasx,\n                canvasy,\n                node.pos[0] + 2,\n                node.pos[1] + 2 - title_height,\n                title_height - 4,\n                title_height - 4\n            )\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    /**\n     * returns the INDEX if a position (in graph space) is on top of a node input slot\n     * @method isOverNodeInput\n     **/\n    LGraphCanvas.prototype.isOverNodeInput = function(\n        node,\n        canvasx,\n        canvasy,\n        slot_pos\n    ) {\n        if (node.inputs) {\n            for (var i = 0, l = node.inputs.length; i < l; ++i) {\n                var input = node.inputs[i];\n                var link_pos = node.getConnectionPos(true, i);\n                var is_inside = false;\n                if (node.horizontal) {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 5,\n                        link_pos[1] - 10,\n                        10,\n                        20\n                    );\n                } else {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        40,\n                        10\n                    );\n                }\n                if (is_inside) {\n                    if (slot_pos) {\n                        slot_pos[0] = link_pos[0];\n                        slot_pos[1] = link_pos[1];\n                    }\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n    \n    /**\n     * returns the INDEX if a position (in graph space) is on top of a node output slot\n     * @method isOverNodeOuput\n     **/\n    LGraphCanvas.prototype.isOverNodeOutput = function(\n        node,\n        canvasx,\n        canvasy,\n        slot_pos\n    ) {\n        if (node.outputs) {\n            for (var i = 0, l = node.outputs.length; i < l; ++i) {\n                var output = node.outputs[i];\n                var link_pos = node.getConnectionPos(false, i);\n                var is_inside = false;\n                if (node.horizontal) {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 5,\n                        link_pos[1] - 10,\n                        10,\n                        20\n                    );\n                } else {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        40,\n                        10\n                    );\n                }\n                if (is_inside) {\n                    if (slot_pos) {\n                        slot_pos[0] = link_pos[0];\n                        slot_pos[1] = link_pos[1];\n                    }\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * process a key event\n     * @method processKey\n     **/\n    LGraphCanvas.prototype.processKey = function(e) {\n        if (!this.graph) {\n            return;\n        }\n\n        var block_default = false;\n        //console.log(e); //debug\n\n        if (e.target.localName == \"input\") {\n            return;\n        }\n\n        if (e.type == \"keydown\") {\n            if (e.keyCode == 32) {\n                //space\n                this.dragging_canvas = true;\n                block_default = true;\n            }\n            \n            if (e.keyCode == 27) {\n                //esc\n                if(this.node_panel) this.node_panel.close();\n                if(this.options_panel) this.options_panel.close();\n                block_default = true;\n            }\n\n            //select all Control A\n            if (e.keyCode == 65 && e.ctrlKey) {\n                this.selectNodes();\n                block_default = true;\n            }\n\n            if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) {\n                //copy\n                if (this.selected_nodes) {\n                    this.copyToClipboard();\n                    block_default = true;\n                }\n            }\n\n            if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) {\n                //paste\n                this.pasteFromClipboard(e.shiftKey);\n            }\n\n            //delete or backspace\n            if (e.keyCode == 46 || e.keyCode == 8) {\n                if (\n                    e.target.localName != \"input\" &&\n                    e.target.localName != \"textarea\"\n                ) {\n                    this.deleteSelectedNodes();\n                    block_default = true;\n                }\n            }\n\n            //collapse\n            //...\n\n            //TODO\n            if (this.selected_nodes) {\n                for (var i in this.selected_nodes) {\n                    if (this.selected_nodes[i].onKeyDown) {\n                        this.selected_nodes[i].onKeyDown(e);\n                    }\n                }\n            }\n        } else if (e.type == \"keyup\") {\n            if (e.keyCode == 32) {\n                // space\n                this.dragging_canvas = false;\n            }\n\n            if (this.selected_nodes) {\n                for (var i in this.selected_nodes) {\n                    if (this.selected_nodes[i].onKeyUp) {\n                        this.selected_nodes[i].onKeyUp(e);\n                    }\n                }\n            }\n        }\n\n        this.graph.change();\n\n        if (block_default) {\n            e.preventDefault();\n            e.stopImmediatePropagation();\n            return false;\n        }\n    };\n\n    LGraphCanvas.prototype.copyToClipboard = function() {\n        var clipboard_info = {\n            nodes: [],\n            links: []\n        };\n        var index = 0;\n        var selected_nodes_array = [];\n        for (var i in this.selected_nodes) {\n            var node = this.selected_nodes[i];\n            if (node.clonable === false)\n                continue;\n            node._relative_id = index;\n            selected_nodes_array.push(node);\n            index += 1;\n        }\n\n        for (var i = 0; i < selected_nodes_array.length; ++i) {\n            var node = selected_nodes_array[i];\n            if(node.clonable === false)\n                continue;\n            var cloned = node.clone();\n            if(!cloned)\n            {\n                console.warn(\"node type not found: \" + node.type );\n                continue;\n            }\n            clipboard_info.nodes.push(cloned.serialize());\n            if (node.inputs && node.inputs.length) {\n                for (var j = 0; j < node.inputs.length; ++j) {\n                    var input = node.inputs[j];\n                    if (!input || input.link == null) {\n                        continue;\n                    }\n                    var link_info = this.graph.links[input.link];\n                    if (!link_info) {\n                        continue;\n                    }\n                    var target_node = this.graph.getNodeById(\n                        link_info.origin_id\n                    );\n                    if (!target_node) {\n                        continue;\n                    }\n                    clipboard_info.links.push([\n                        target_node._relative_id,\n                        link_info.origin_slot, //j,\n                        node._relative_id,\n                        link_info.target_slot,\n                        target_node.id\n                    ]);\n                }\n            }\n        }\n        localStorage.setItem(\n            \"litegrapheditor_clipboard\",\n            JSON.stringify(clipboard_info)\n        );\n    };\n\n    LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) {\n        // if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior\n        if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {\n            return;\n        }\n        var data = localStorage.getItem(\"litegrapheditor_clipboard\");\n        if (!data) {\n            return;\n        }\n\n\t\tthis.graph.beforeChange();\n\n        //create nodes\n        var clipboard_info = JSON.parse(data);\n        // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos\n        var posMin = false;\n        var posMinIndexes = false;\n        for (var i = 0; i < clipboard_info.nodes.length; ++i) {\n            if (posMin){\n                if(posMin[0]>clipboard_info.nodes[i].pos[0]){\n                    posMin[0] = clipboard_info.nodes[i].pos[0];\n                    posMinIndexes[0] = i;\n                }\n                if(posMin[1]>clipboard_info.nodes[i].pos[1]){\n                    posMin[1] = clipboard_info.nodes[i].pos[1];\n                    posMinIndexes[1] = i;\n                }\n            }\n            else{\n                posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]];\n                posMinIndexes = [i, i];\n            }\n        }\n        var nodes = [];\n        for (var i = 0; i < clipboard_info.nodes.length; ++i) {\n            var node_data = clipboard_info.nodes[i];\n            var node = LiteGraph.createNode(node_data.type);\n            if (node) {\n                node.configure(node_data);\n        \n\t\t\t\t//paste in last known mouse position\n                node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5;\n                node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5;\n\n                this.graph.add(node,{doProcessChange:false});\n                \n                nodes.push(node);\n            }\n        }\n\n        //create links\n        for (var i = 0; i < clipboard_info.links.length; ++i) {\n            var link_info = clipboard_info.links[i];\n            var origin_node;\n            var origin_node_relative_id = link_info[0];\n            if (origin_node_relative_id != null) {\n                origin_node = nodes[origin_node_relative_id];\n            } else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {\n                var origin_node_id = link_info[4];\n                if (origin_node_id) {\n                    origin_node = this.graph.getNodeById(origin_node_id);\n                }\n            }\n            var target_node = nodes[link_info[2]];\n\t\t\tif( origin_node && target_node )\n\t            origin_node.connect(link_info[1], target_node, link_info[3]);\n\t\t\telse\n\t\t\t\tconsole.warn(\"Warning, nodes missing on pasting\");\n        }\n\n        this.selectNodes(nodes);\n\n\t\tthis.graph.afterChange();\n    };\n\n    /**\n     * process a item drop event on top the canvas\n     * @method processDrop\n     **/\n    LGraphCanvas.prototype.processDrop = function(e) {\n        e.preventDefault();\n        this.adjustMouseEvent(e);\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\t\tif(!is_inside){\n\t\t\treturn;\n\t\t\t// --- BREAK ---\n\t\t}\n\n        var pos = [e.canvasX, e.canvasY];\n\n\n        var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null;\n\n        if (!node) {\n            var r = null;\n            if (this.onDropItem) {\n                r = this.onDropItem(event);\n            }\n            if (!r) {\n                this.checkDropItem(e);\n            }\n            return;\n        }\n\n        if (node.onDropFile || node.onDropData) {\n            var files = e.dataTransfer.files;\n            if (files && files.length) {\n                for (var i = 0; i < files.length; i++) {\n                    var file = e.dataTransfer.files[0];\n                    var filename = file.name;\n                    var ext = LGraphCanvas.getFileExtension(filename);\n                    //console.log(file);\n\n                    if (node.onDropFile) {\n                        node.onDropFile(file);\n                    }\n\n                    if (node.onDropData) {\n                        //prepare reader\n                        var reader = new FileReader();\n                        reader.onload = function(event) {\n                            //console.log(event.target);\n                            var data = event.target.result;\n                            node.onDropData(data, filename, file);\n                        };\n\n                        //read data\n                        var type = file.type.split(\"/\")[0];\n                        if (type == \"text\" || type == \"\") {\n                            reader.readAsText(file);\n                        } else if (type == \"image\") {\n                            reader.readAsDataURL(file);\n                        } else {\n                            reader.readAsArrayBuffer(file);\n                        }\n                    }\n                }\n            }\n        }\n\n        if (node.onDropItem) {\n            if (node.onDropItem(event)) {\n                return true;\n            }\n        }\n\n        if (this.onDropItem) {\n            return this.onDropItem(event);\n        }\n\n        return false;\n    };\n\n    //called if the graph doesn't have a default drop item behaviour\n    LGraphCanvas.prototype.checkDropItem = function(e) {\n        if (e.dataTransfer.files.length) {\n            var file = e.dataTransfer.files[0];\n            var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase();\n            var nodetype = LiteGraph.node_types_by_file_extension[ext];\n            if (nodetype) {\n\t\t\t\tthis.graph.beforeChange();\n                var node = LiteGraph.createNode(nodetype.type);\n                node.pos = [e.canvasX, e.canvasY];\n                this.graph.add(node);\n                if (node.onDropFile) {\n                    node.onDropFile(file);\n                }\n\t\t\t\tthis.graph.afterChange();\n            }\n        }\n    };\n\n    LGraphCanvas.prototype.processNodeDblClicked = function(n) {\n        if (this.onShowNodePanel) {\n            this.onShowNodePanel(n);\n        }\n\t\telse\n\t\t{\n\t\t\tthis.showShowNodePanel(n);\n\t\t}\n\n        if (this.onNodeDblClicked) {\n            this.onNodeDblClicked(n);\n        }\n\n        this.setDirty(true);\n    };\n\n    LGraphCanvas.prototype.processNodeSelected = function(node, e) {\n        this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select));\n        if (this.onNodeSelected) {\n            this.onNodeSelected(node);\n        }\n    };\n\n    /**\n     * selects a given node (or adds it to the current selection)\n     * @method selectNode\n     **/\n    LGraphCanvas.prototype.selectNode = function(\n        node,\n        add_to_current_selection\n    ) {\n        if (node == null) {\n            this.deselectAllNodes();\n        } else {\n            this.selectNodes([node], add_to_current_selection);\n        }\n    };\n\n    /**\n     * selects several nodes (or adds them to the current selection)\n     * @method selectNodes\n     **/\n    LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection )\n\t{\n\t\tif (!add_to_current_selection) {\n            this.deselectAllNodes();\n        }\n\n        nodes = nodes || this.graph._nodes;\n\t\tif (typeof nodes == \"string\") nodes = [nodes];\n        for (var i in nodes) {\n            var node = nodes[i];\n            if (node.is_selected) {\n                this.deselectNode(node);\n                continue;\n            }\n\n            if (!node.is_selected && node.onSelected) {\n                node.onSelected();\n            }\n            node.is_selected = true;\n            this.selected_nodes[node.id] = node;\n\n            if (node.inputs) {\n                for (var j = 0; j < node.inputs.length; ++j) {\n                    this.highlighted_links[node.inputs[j].link] = true;\n                }\n            }\n            if (node.outputs) {\n                for (var j = 0; j < node.outputs.length; ++j) {\n                    var out = node.outputs[j];\n                    if (out.links) {\n                        for (var k = 0; k < out.links.length; ++k) {\n                            this.highlighted_links[out.links[k]] = true;\n                        }\n                    }\n                }\n            }\n        }\n\n\t\tif(\tthis.onSelectionChange )\n\t\t\tthis.onSelectionChange( this.selected_nodes );\n\n        this.setDirty(true);\n    };\n\n    /**\n     * removes a node from the current selection\n     * @method deselectNode\n     **/\n    LGraphCanvas.prototype.deselectNode = function(node) {\n        if (!node.is_selected) {\n            return;\n        }\n        if (node.onDeselected) {\n            node.onDeselected();\n        }\n        node.is_selected = false;\n\n        if (this.onNodeDeselected) {\n            this.onNodeDeselected(node);\n        }\n\n        //remove highlighted\n        if (node.inputs) {\n            for (var i = 0; i < node.inputs.length; ++i) {\n                delete this.highlighted_links[node.inputs[i].link];\n            }\n        }\n        if (node.outputs) {\n            for (var i = 0; i < node.outputs.length; ++i) {\n                var out = node.outputs[i];\n                if (out.links) {\n                    for (var j = 0; j < out.links.length; ++j) {\n                        delete this.highlighted_links[out.links[j]];\n                    }\n                }\n            }\n        }\n    };\n\n    /**\n     * removes all nodes from the current selection\n     * @method deselectAllNodes\n     **/\n    LGraphCanvas.prototype.deselectAllNodes = function() {\n        if (!this.graph) {\n            return;\n        }\n        var nodes = this.graph._nodes;\n        for (var i = 0, l = nodes.length; i < l; ++i) {\n            var node = nodes[i];\n            if (!node.is_selected) {\n                continue;\n            }\n            if (node.onDeselected) {\n                node.onDeselected();\n            }\n            node.is_selected = false;\n\t\t\tif (this.onNodeDeselected) {\n\t\t\t\tthis.onNodeDeselected(node);\n\t\t\t}\n        }\n        this.selected_nodes = {};\n        this.current_node = null;\n        this.highlighted_links = {};\n\t\tif(\tthis.onSelectionChange )\n\t\t\tthis.onSelectionChange( this.selected_nodes );\n        this.setDirty(true);\n    };\n\n    /**\n     * deletes all nodes in the current selection from the graph\n     * @method deleteSelectedNodes\n     **/\n    LGraphCanvas.prototype.deleteSelectedNodes = function() {\n\n\t\tthis.graph.beforeChange();\n\n        for (var i in this.selected_nodes) {\n            var node = this.selected_nodes[i];\n\n\t\t\tif(node.block_delete)\n\t\t\t\tcontinue;\n\n\t\t\t//autoconnect when possible (very basic, only takes into account first input-output)\n\t\t\tif(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) \n\t\t\t{\n\t\t\t\tvar input_link = node.graph.links[ node.inputs[0].link ];\n\t\t\t\tvar output_link = node.graph.links[ node.outputs[0].links[0] ];\n\t\t\t\tvar input_node = node.getInputNode(0);\n\t\t\t\tvar output_node = node.getOutputNodes(0)[0];\n\t\t\t\tif(input_node && output_node)\n\t\t\t\t\tinput_node.connect( input_link.origin_slot, output_node, output_link.target_slot );\n\t\t\t}\n            this.graph.remove(node);\n\t\t\tif (this.onNodeDeselected) {\n\t\t\t\tthis.onNodeDeselected(node);\n\t\t\t}\n        }\n        this.selected_nodes = {};\n        this.current_node = null;\n        this.highlighted_links = {};\n        this.setDirty(true);\n\t\tthis.graph.afterChange();\n    };\n    \n    /**\n     * centers the camera on a given node\n     * @method centerOnNode\n     **/\n    LGraphCanvas.prototype.centerOnNode = function(node) {\n        this.ds.offset[0] =\n            -node.pos[0] -\n            node.size[0] * 0.5 +\n            (this.canvas.width * 0.5) / this.ds.scale;\n        this.ds.offset[1] =\n            -node.pos[1] -\n            node.size[1] * 0.5 +\n            (this.canvas.height * 0.5) / this.ds.scale;\n        this.setDirty(true, true);\n    };\n\n    /**\n     * adds some useful properties to a mouse event, like the position in graph coordinates\n     * @method adjustMouseEvent\n     **/\n    LGraphCanvas.prototype.adjustMouseEvent = function(e) {\n\tvar clientX_rel = 0;\n        var clientY_rel = 0;\n\t    \n    \tif (this.canvas) {\n            var b = this.canvas.getBoundingClientRect();\n            clientX_rel = e.clientX - b.left;\n            clientY_rel = e.clientY - b.top;\n        } else {\n        \tclientX_rel = e.clientX;\n        \tclientY_rel = e.clientY;\n        }\n    \t\n        // e.deltaX = clientX_rel - this.last_mouse_position[0];\n        // e.deltaY = clientY_rel- this.last_mouse_position[1];\n\n        this.last_mouse_position[0] = clientX_rel;\n        this.last_mouse_position[1] = clientY_rel;\n\n        e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0];\n        e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1];\n        \n        //console.log(\"pointerevents: adjustMouseEvent \"+e.clientX+\":\"+e.clientY+\" \"+clientX_rel+\":\"+clientY_rel+\" \"+e.canvasX+\":\"+e.canvasY);\n    };\n\n    /**\n     * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom\n     * @method setZoom\n     **/\n    LGraphCanvas.prototype.setZoom = function(value, zooming_center) {\n        this.ds.changeScale(value, zooming_center);\n        /*\n\tif(!zooming_center && this.canvas)\n\t\tzooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5];\n\n\tvar center = this.convertOffsetToCanvas( zooming_center );\n\n\tthis.ds.scale = value;\n\n\tif(this.scale > this.max_zoom)\n\t\tthis.scale = this.max_zoom;\n\telse if(this.scale < this.min_zoom)\n\t\tthis.scale = this.min_zoom;\n\n\tvar new_center = this.convertOffsetToCanvas( zooming_center );\n\tvar delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];\n\n\tthis.offset[0] += delta_offset[0];\n\tthis.offset[1] += delta_offset[1];\n\t*/\n\n        this.dirty_canvas = true;\n        this.dirty_bgcanvas = true;\n    };\n\n    /**\n     * converts a coordinate from graph coordinates to canvas2D coordinates\n     * @method convertOffsetToCanvas\n     **/\n    LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) {\n        return this.ds.convertOffsetToCanvas(pos, out);\n    };\n\n    /**\n     * converts a coordinate from Canvas2D coordinates to graph space\n     * @method convertCanvasToOffset\n     **/\n    LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) {\n        return this.ds.convertCanvasToOffset(pos, out);\n    };\n\n    //converts event coordinates from canvas2D to graph coordinates\n    LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) {\n        var rect = this.canvas.getBoundingClientRect();\n        return this.convertCanvasToOffset([\n            e.clientX - rect.left,\n            e.clientY - rect.top\n        ]);\n    };\n\n    /**\n     * brings a node to front (above all other nodes)\n     * @method bringToFront\n     **/\n    LGraphCanvas.prototype.bringToFront = function(node) {\n        var i = this.graph._nodes.indexOf(node);\n        if (i == -1) {\n            return;\n        }\n\n        this.graph._nodes.splice(i, 1);\n        this.graph._nodes.push(node);\n    };\n\n    /**\n     * sends a node to the back (below all other nodes)\n     * @method sendToBack\n     **/\n    LGraphCanvas.prototype.sendToBack = function(node) {\n        var i = this.graph._nodes.indexOf(node);\n        if (i == -1) {\n            return;\n        }\n\n        this.graph._nodes.splice(i, 1);\n        this.graph._nodes.unshift(node);\n    };\n\n    /* Interaction */\n\n    /* LGraphCanvas render */\n    var temp = new Float32Array(4);\n\n    /**\n     * checks which nodes are visible (inside the camera area)\n     * @method computeVisibleNodes\n     **/\n    LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) {\n        var visible_nodes = out || [];\n        visible_nodes.length = 0;\n        nodes = nodes || this.graph._nodes;\n        for (var i = 0, l = nodes.length; i < l; ++i) {\n            var n = nodes[i];\n\n            //skip rendering nodes in live mode\n            if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) {\n                continue;\n            }\n\n            if (!overlapBounding(this.visible_area, n.getBounding(temp, true))) {\n                continue;\n            } //out of the visible area\n\n            visible_nodes.push(n);\n        }\n        return visible_nodes;\n    };\n\n    /**\n     * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)\n     * @method draw\n     **/\n    LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) {\n        if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) {\n            return;\n        }\n\n        //fps counting\n        var now = LiteGraph.getTime();\n        this.render_time = (now - this.last_draw_time) * 0.001;\n        this.last_draw_time = now;\n\n        if (this.graph) {\n            this.ds.computeVisibleArea(this.viewport);\n        }\n\n        if (\n            this.dirty_bgcanvas ||\n            force_bgcanvas ||\n            this.always_render_background ||\n            (this.graph &&\n                this.graph._last_trigger_time &&\n                now - this.graph._last_trigger_time < 1000)\n        ) {\n            this.drawBackCanvas();\n        }\n\n        if (this.dirty_canvas || force_canvas) {\n            this.drawFrontCanvas();\n        }\n\n        this.fps = this.render_time ? 1.0 / this.render_time : 0;\n        this.frame += 1;\n    };\n\n    /**\n     * draws the front canvas (the one containing all the nodes)\n     * @method drawFrontCanvas\n     **/\n    LGraphCanvas.prototype.drawFrontCanvas = function() {\n        this.dirty_canvas = false;\n\n        if (!this.ctx) {\n            this.ctx = this.bgcanvas.getContext(\"2d\");\n        }\n        var ctx = this.ctx;\n        if (!ctx) {\n            //maybe is using webgl...\n            return;\n        }\n\n        var canvas = this.canvas;\n        if ( ctx.start2D && !this.viewport ) {\n            ctx.start2D();\n\t\t\tctx.restore();\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n        }\n\n        //clip dirty area if there is one, otherwise work in full canvas\n\t\tvar area = this.viewport || this.dirty_area;\n        if (area) {\n            ctx.save();\n            ctx.beginPath();\n            ctx.rect( area[0],area[1],area[2],area[3] );\n            ctx.clip();\n        }\n\n        //clear\n        //canvas.width = canvas.width;\n        if (this.clear_background) {\n\t\t\tif(area)\n\t            ctx.clearRect( area[0],area[1],area[2],area[3] );\n\t\t\telse\n\t            ctx.clearRect(0, 0, canvas.width, canvas.height);\n        }\n\n        //draw bg canvas\n        if (this.bgcanvas == this.canvas) {\n            this.drawBackCanvas();\n        } else {\n            ctx.drawImage( this.bgcanvas, 0, 0 );\n        }\n\n        //rendering\n        if (this.onRender) {\n            this.onRender(canvas, ctx);\n        }\n\n        //info widget\n        if (this.show_info) {\n            this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0 );\n        }\n\n        if (this.graph) {\n            //apply transformations\n            ctx.save();\n            this.ds.toCanvasContext(ctx);\n\n            //draw nodes\n            var drawn_nodes = 0;\n            var visible_nodes = this.computeVisibleNodes(\n                null,\n                this.visible_nodes\n            );\n\n            for (var i = 0; i < visible_nodes.length; ++i) {\n                var node = visible_nodes[i];\n\n                //transform coords system\n                ctx.save();\n                ctx.translate(node.pos[0], node.pos[1]);\n\n                //Draw\n                this.drawNode(node, ctx);\n                drawn_nodes += 1;\n\n                //Restore\n                ctx.restore();\n            }\n\n            //on top (debug)\n            if (this.render_execution_order) {\n                this.drawExecutionOrder(ctx);\n            }\n\n            //connections ontop?\n            if (this.graph.config.links_ontop) {\n                if (!this.live_mode) {\n                    this.drawConnections(ctx);\n                }\n            }\n\n            //current connection (the one being dragged by the mouse)\n            if (this.connecting_pos != null) {\n                ctx.lineWidth = this.connections_width;\n                var link_color = null;\n                \n                var connInOrOut = this.connecting_output || this.connecting_input;\n\n                var connType = connInOrOut.type;\n                var connDir = connInOrOut.dir;\n\t\t\t\tif(connDir == null)\n\t\t\t\t{\n\t\t\t\t\tif (this.connecting_output)\n\t\t\t\t\t\tconnDir = this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT;\n\t\t\t\t\telse\n\t\t\t\t\t\tconnDir = this.connecting_node.horizontal ? LiteGraph.UP : LiteGraph.LEFT;\n\t\t\t\t}\n                var connShape = connInOrOut.shape;\n                \n                switch (connType) {\n                    case LiteGraph.EVENT:\n                        link_color = LiteGraph.EVENT_LINK_COLOR;\n                        break;\n                    default:\n                        link_color = LiteGraph.CONNECTING_LINK_COLOR;\n                }\n\n                //the connection being dragged by the mouse\n                this.renderLink(\n                    ctx,\n                    this.connecting_pos,\n                    [this.graph_mouse[0], this.graph_mouse[1]],\n                    null,\n                    false,\n                    null,\n                    link_color,\n                    connDir,\n                    LiteGraph.CENTER\n                );\n\n                ctx.beginPath();\n                if (\n                    connType === LiteGraph.EVENT ||\n                    connShape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(\n                        this.connecting_pos[0] - 6 + 0.5,\n                        this.connecting_pos[1] - 5 + 0.5,\n                        14,\n                        10\n                    );\n\t                ctx.fill();\n\t\t\t\t\tctx.beginPath();\n                    ctx.rect(\n                        this.graph_mouse[0] - 6 + 0.5,\n                        this.graph_mouse[1] - 5 + 0.5,\n                        14,\n                        10\n                    );\n                } else if (connShape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5);\n                    ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5);\n                    ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5);\n                    ctx.closePath();\n                } \n                else {\n                    ctx.arc(\n                        this.connecting_pos[0],\n                        this.connecting_pos[1],\n                        4,\n                        0,\n                        Math.PI * 2\n                    );\n\t                ctx.fill();\n\t\t\t\t\tctx.beginPath();\n                    ctx.arc(\n                        this.graph_mouse[0],\n                        this.graph_mouse[1],\n                        4,\n                        0,\n                        Math.PI * 2\n                    );\n                }\n                ctx.fill();\n\n                ctx.fillStyle = \"#ffcc00\";\n                if (this._highlight_input) {\n                    ctx.beginPath();\n                    var shape = this._highlight_input_slot.shape;\n                    if (shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5);\n                        ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5);\n                        ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else {\n                        ctx.arc(\n                            this._highlight_input[0],\n                            this._highlight_input[1],\n                            6,\n                            0,\n                            Math.PI * 2\n                        );\n                    }\n                    ctx.fill();\n                }\n                if (this._highlight_output) {\n                    ctx.beginPath();\n                    if (shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5);\n                        ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5);\n                        ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else {\n                        ctx.arc(\n                            this._highlight_output[0],\n                            this._highlight_output[1],\n                            6,\n                            0,\n                            Math.PI * 2\n                        );\n                    }\n                    ctx.fill();\n                }\n            }\n\n\t\t\t//the selection rectangle\n            if (this.dragging_rectangle) {\n                ctx.strokeStyle = \"#FFF\";\n                ctx.strokeRect(\n                    this.dragging_rectangle[0],\n                    this.dragging_rectangle[1],\n                    this.dragging_rectangle[2],\n                    this.dragging_rectangle[3]\n                );\n            }\n\n\t\t\t//on top of link center\n\t\t\tif(this.over_link_center && this.render_link_tooltip)\n\t\t\t\tthis.drawLinkTooltip( ctx, this.over_link_center );\n\t\t\telse\n\t\t\t\tif(this.onDrawLinkTooltip) //to remove\n\t\t\t\t\tthis.onDrawLinkTooltip(ctx,null);\n\n\t\t\t//custom info\n            if (this.onDrawForeground) {\n                this.onDrawForeground(ctx, this.visible_rect);\n            }\n\n            ctx.restore();\n        }\n\n\t\t//draws panel in the corner \n\t\tif (this._graph_stack && this._graph_stack.length) {\n\t\t\tthis.drawSubgraphPanel( ctx );\n\t\t}\n\n\n        if (this.onDrawOverlay) {\n            this.onDrawOverlay(ctx);\n        }\n\n        if (area){\n            ctx.restore();\n        }\n\n        if (ctx.finish2D) {\n            //this is a function I use in webgl renderer\n            ctx.finish2D();\n        }\n    };\n\n    /**\n     * draws the panel in the corner that shows subgraph properties\n     * @method drawSubgraphPanel\n     **/\n    LGraphCanvas.prototype.drawSubgraphPanel = function (ctx) {\n        var subgraph = this.graph;\n        var subnode = subgraph._subgraph_node;\n        if (!subnode) {\n            console.warn(\"subgraph without subnode\");\n            return;\n        }\n        this.drawSubgraphPanelLeft(subgraph, subnode, ctx)\n        this.drawSubgraphPanelRight(subgraph, subnode, ctx)\n    }\n\n    LGraphCanvas.prototype.drawSubgraphPanelLeft = function (subgraph, subnode, ctx) {\n        var num = subnode.inputs ? subnode.inputs.length : 0;\n        var w = 200;\n        var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6);\n\n        ctx.fillStyle = \"#111\";\n        ctx.globalAlpha = 0.8;\n        ctx.beginPath();\n        ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]);\n        ctx.fill();\n        ctx.globalAlpha = 1;\n\n        ctx.fillStyle = \"#888\";\n        ctx.font = \"14px Arial\";\n        ctx.textAlign = \"left\";\n        ctx.fillText(\"Graph Inputs\", 20, 34);\n        // var pos = this.mouse;\n\n        if (this.drawButton(w - 20, 20, 20, 20, \"X\", \"#151515\")) {\n            this.closeSubgraph();\n            return;\n        }\n\n        var y = 50;\n        ctx.font = \"14px Arial\";\n        if (subnode.inputs)\n            for (var i = 0; i < subnode.inputs.length; ++i) {\n                var input = subnode.inputs[i];\n                if (input.not_subgraph_input)\n                    continue;\n\n                //input button clicked\n                if (this.drawButton(20, y + 2, w - 20, h - 2)) {\n                    var type = subnode.constructor.input_node_type || \"graph/input\";\n                    this.graph.beforeChange();\n                    var newnode = LiteGraph.createNode(type);\n                    if (newnode) {\n                        subgraph.add(newnode);\n                        this.block_click = false;\n                        this.last_click_position = null;\n                        this.selectNodes([newnode]);\n                        this.node_dragged = newnode;\n                        this.dragging_canvas = false;\n                        newnode.setProperty(\"name\", input.name);\n                        newnode.setProperty(\"type\", input.type);\n                        this.node_dragged.pos[0] = this.graph_mouse[0] - 5;\n                        this.node_dragged.pos[1] = this.graph_mouse[1] - 5;\n                        this.graph.afterChange();\n                    }\n                    else\n                        console.error(\"graph input node not found:\", type);\n                }\n                ctx.fillStyle = \"#9C9\";\n                ctx.beginPath();\n                ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI);\n                ctx.fill();\n                ctx.fillStyle = \"#AAA\";\n                ctx.fillText(input.name, 30, y + h * 0.75);\n                // var tw = ctx.measureText(input.name);\n                ctx.fillStyle = \"#777\";\n                ctx.fillText(input.type, 130, y + h * 0.75);\n                y += h;\n            }\n        //add + button\n        if (this.drawButton(20, y + 2, w - 20, h - 2, \"+\", \"#151515\", \"#222\")) {\n            this.showSubgraphPropertiesDialog(subnode);\n        }\n    }\n    LGraphCanvas.prototype.drawSubgraphPanelRight = function (subgraph, subnode, ctx) {\n        var num = subnode.outputs ? subnode.outputs.length : 0;\n        var canvas_w = this.bgcanvas.width\n        var w = 200;\n        var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6);\n\n        ctx.fillStyle = \"#111\";\n        ctx.globalAlpha = 0.8;\n        ctx.beginPath();\n        ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]);\n        ctx.fill();\n        ctx.globalAlpha = 1;\n\n        ctx.fillStyle = \"#888\";\n        ctx.font = \"14px Arial\";\n        ctx.textAlign = \"left\";\n        var title_text = \"Graph Outputs\"\n        var tw = ctx.measureText(title_text).width\n        ctx.fillText(title_text, (canvas_w - tw) - 20, 34);\n        // var pos = this.mouse;\n        if (this.drawButton(canvas_w - w, 20, 20, 20, \"X\", \"#151515\")) {\n            this.closeSubgraph();\n            return;\n        }\n\n        var y = 50;\n        ctx.font = \"14px Arial\";\n        if (subnode.outputs)\n            for (var i = 0; i < subnode.outputs.length; ++i) {\n                var output = subnode.outputs[i];\n                if (output.not_subgraph_input)\n                    continue;\n\n                //output button clicked\n                if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2)) {\n                    var type = subnode.constructor.output_node_type || \"graph/output\";\n                    this.graph.beforeChange();\n                    var newnode = LiteGraph.createNode(type);\n                    if (newnode) {\n                        subgraph.add(newnode);\n                        this.block_click = false;\n                        this.last_click_position = null;\n                        this.selectNodes([newnode]);\n                        this.node_dragged = newnode;\n                        this.dragging_canvas = false;\n                        newnode.setProperty(\"name\", output.name);\n                        newnode.setProperty(\"type\", output.type);\n                        this.node_dragged.pos[0] = this.graph_mouse[0] - 5;\n                        this.node_dragged.pos[1] = this.graph_mouse[1] - 5;\n                        this.graph.afterChange();\n                    }\n                    else\n                        console.error(\"graph input node not found:\", type);\n                }\n                ctx.fillStyle = \"#9C9\";\n                ctx.beginPath();\n                ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI);\n                ctx.fill();\n                ctx.fillStyle = \"#AAA\";\n                ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75);\n                // var tw = ctx.measureText(input.name);\n                ctx.fillStyle = \"#777\";\n                ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75);\n                y += h;\n            }\n        //add + button\n        if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2, \"+\", \"#151515\", \"#222\")) {\n            this.showSubgraphPropertiesDialogRight(subnode);\n        }\n    }\n\t//Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm\n\tLGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor )\n\t{\n\t\tvar ctx = this.ctx;\n\t\tbgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR;\n\t\thovercolor = hovercolor || \"#555\";\n\t\ttextcolor = textcolor || LiteGraph.NODE_TEXT_COLOR;\n\t\tvar pos = this.ds.convertOffsetToCanvas(this.graph_mouse);\n\t\tvar hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tpos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null;\n        if(pos) {\n            var rect = this.canvas.getBoundingClientRect();\n            pos[0] -= rect.left;\n            pos[1] -= rect.top;\n        }\n\t\tvar clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\n\t\tctx.fillStyle = hover ? hovercolor : bgcolor;\n\t\tif(clicked)\n\t\t\tctx.fillStyle = \"#AAA\";\n\t\tctx.beginPath();\n\t\tctx.roundRect(x,y,w,h,[4] );\n\t\tctx.fill();\n\n\t\tif(text != null)\n\t\t{\n\t\t\tif(text.constructor == String)\n\t\t\t{\n\t\t\t\tctx.fillStyle = textcolor;\n\t\t\t\tctx.textAlign = \"center\";\n\t\t\t\tctx.font = ((h * 0.65)|0) + \"px Arial\";\n\t\t\t\tctx.fillText( text, x + w * 0.5,y + h * 0.75 );\n\t\t\t\tctx.textAlign = \"left\";\n\t\t\t}\n\t\t}\n\n\t\tvar was_clicked = clicked && !this.block_click;\n\t\tif(clicked)\n\t\t\tthis.blockClick();\n\t\treturn was_clicked;\n\t}\n\n\tLGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click )\n\t{\n\t\tvar pos = this.mouse;\n\t\tvar hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tpos = this.last_click_position;\n\t\tvar clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tvar was_clicked = clicked && !this.block_click;\n\t\tif(clicked && hold_click)\n\t\t\tthis.blockClick();\n\t\treturn was_clicked;\n\t}\n\n    /**\n     * draws some useful stats in the corner of the canvas\n     * @method renderInfo\n     **/\n    LGraphCanvas.prototype.renderInfo = function(ctx, x, y) {\n        x = x || 10;\n        y = y || this.canvas.height - 80;\n\n        ctx.save();\n        ctx.translate(x, y);\n\n        ctx.font = \"10px Arial\";\n        ctx.fillStyle = \"#888\";\n\t\tctx.textAlign = \"left\";\n        if (this.graph) {\n            ctx.fillText( \"T: \" + this.graph.globaltime.toFixed(2) + \"s\", 5, 13 * 1 );\n            ctx.fillText(\"I: \" + this.graph.iteration, 5, 13 * 2 );\n            ctx.fillText(\"N: \" + this.graph._nodes.length + \" [\" + this.visible_nodes.length + \"]\", 5, 13 * 3 );\n            ctx.fillText(\"V: \" + this.graph._version, 5, 13 * 4);\n            ctx.fillText(\"FPS:\" + this.fps.toFixed(2), 5, 13 * 5);\n        } else {\n            ctx.fillText(\"No graph selected\", 5, 13 * 1);\n        }\n        ctx.restore();\n    };\n\n    /**\n     * draws the back canvas (the one containing the background and the connections)\n     * @method drawBackCanvas\n     **/\n    LGraphCanvas.prototype.drawBackCanvas = function() {\n        var canvas = this.bgcanvas;\n        if (\n            canvas.width != this.canvas.width ||\n            canvas.height != this.canvas.height\n        ) {\n            canvas.width = this.canvas.width;\n            canvas.height = this.canvas.height;\n        }\n\n        if (!this.bgctx) {\n            this.bgctx = this.bgcanvas.getContext(\"2d\");\n        }\n        var ctx = this.bgctx;\n        if (ctx.start) {\n            ctx.start();\n        }\n\n\t\tvar viewport = this.viewport || [0,0,ctx.canvas.width,ctx.canvas.height];\n\n        //clear\n        if (this.clear_background) {\n            ctx.clearRect( viewport[0], viewport[1], viewport[2], viewport[3] );\n        }\n\n\t\t//show subgraph stack header\n        if (this._graph_stack && this._graph_stack.length) {\n            ctx.save();\n            var parent_graph = this._graph_stack[this._graph_stack.length - 1];\n            var subgraph_node = this.graph._subgraph_node;\n            ctx.strokeStyle = subgraph_node.bgcolor;\n            ctx.lineWidth = 10;\n            ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2);\n            ctx.lineWidth = 1;\n            ctx.font = \"40px Arial\";\n            ctx.textAlign = \"center\";\n            ctx.fillStyle = subgraph_node.bgcolor || \"#AAA\";\n            var title = \"\";\n            for (var i = 1; i < this._graph_stack.length; ++i) {\n                title +=\n                    this._graph_stack[i]._subgraph_node.getTitle() + \" >> \";\n            }\n            ctx.fillText(\n                title + subgraph_node.getTitle(),\n                canvas.width * 0.5,\n                40\n            );\n            ctx.restore();\n        }\n\n        var bg_already_painted = false;\n        if (this.onRenderBackground) {\n            bg_already_painted = this.onRenderBackground(canvas, ctx);\n        }\n\n        //reset in case of error\n        if ( !this.viewport )\n\t\t{\n\t        ctx.restore();\n\t\t    ctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t}\n        this.visible_links.length = 0;\n\n        if (this.graph) {\n            //apply transformations\n            ctx.save();\n            this.ds.toCanvasContext(ctx);\n\n            //render BG\n            if ( this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color )\n            {\n                ctx.fillStyle = this.clear_background_color;\n                ctx.fillRect(\n                    this.visible_area[0],\n                    this.visible_area[1],\n                    this.visible_area[2],\n                    this.visible_area[3]\n                );\n            }\n\n            if (\n                this.background_image &&\n                this.ds.scale > 0.5 &&\n                !bg_already_painted\n            ) {\n                if (this.zoom_modify_alpha) {\n                    ctx.globalAlpha =\n                        (1.0 - 0.5 / this.ds.scale) * this.editor_alpha;\n                } else {\n                    ctx.globalAlpha = this.editor_alpha;\n                }\n                ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = \n                if (\n                    !this._bg_img ||\n                    this._bg_img.name != this.background_image\n                ) {\n                    this._bg_img = new Image();\n                    this._bg_img.name = this.background_image;\n                    this._bg_img.src = this.background_image;\n                    var that = this;\n                    this._bg_img.onload = function() {\n                        that.draw(true, true);\n                    };\n                }\n\n                var pattern = null;\n                if (this._pattern == null && this._bg_img.width > 0) {\n                    pattern = ctx.createPattern(this._bg_img, \"repeat\");\n                    this._pattern_img = this._bg_img;\n                    this._pattern = pattern;\n                } else {\n                    pattern = this._pattern;\n                }\n                if (pattern) {\n                    ctx.fillStyle = pattern;\n                    ctx.fillRect(\n                        this.visible_area[0],\n                        this.visible_area[1],\n                        this.visible_area[2],\n                        this.visible_area[3]\n                    );\n                    ctx.fillStyle = \"transparent\";\n                }\n\n                ctx.globalAlpha = 1.0;\n                ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled\n            }\n\n            //groups\n            if (this.graph._groups.length && !this.live_mode) {\n                this.drawGroups(canvas, ctx);\n            }\n\n            if (this.onDrawBackground) {\n                this.onDrawBackground(ctx, this.visible_area);\n            }\n            if (this.onBackgroundRender) {\n                //LEGACY\n                console.error(\n                    \"WARNING! onBackgroundRender deprecated, now is named onDrawBackground \"\n                );\n                this.onBackgroundRender = null;\n            }\n\n            //DEBUG: show clipping area\n            //ctx.fillStyle = \"red\";\n            //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20);\n\n            //bg\n            if (this.render_canvas_border) {\n                ctx.strokeStyle = \"#235\";\n                ctx.strokeRect(0, 0, canvas.width, canvas.height);\n            }\n\n            if (this.render_connections_shadows) {\n                ctx.shadowColor = \"#000\";\n                ctx.shadowOffsetX = 0;\n                ctx.shadowOffsetY = 0;\n                ctx.shadowBlur = 6;\n            } else {\n                ctx.shadowColor = \"rgba(0,0,0,0)\";\n            }\n\n            //draw connections\n            if (!this.live_mode) {\n                this.drawConnections(ctx);\n            }\n\n            ctx.shadowColor = \"rgba(0,0,0,0)\";\n\n            //restore state\n            ctx.restore();\n        }\n\n        if (ctx.finish) {\n            ctx.finish();\n        }\n\n        this.dirty_bgcanvas = false;\n        this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas\n    };\n\n    var temp_vec2 = new Float32Array(2);\n\n    /**\n     * draws the given node inside the canvas\n     * @method drawNode\n     **/\n    LGraphCanvas.prototype.drawNode = function(node, ctx) {\n        var glow = false;\n        this.current_node = node;\n\n        var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR;\n        var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;\n\n        //shadow and glow\n        if (node.mouseOver) {\n            glow = true;\n        }\n\n        var low_quality = this.ds.scale < 0.6; //zoomed out\n\n        //only render if it forces it to do it\n        if (this.live_mode) {\n            if (!node.flags.collapsed) {\n                ctx.shadowColor = \"transparent\";\n                if (node.onDrawForeground) {\n                    node.onDrawForeground(ctx, this, this.canvas);\n                }\n            }\n            return;\n        }\n\n        var editor_alpha = this.editor_alpha;\n        ctx.globalAlpha = editor_alpha;\n\n        if (this.render_shadows && !low_quality) {\n            ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;\n            ctx.shadowOffsetX = 2 * this.ds.scale;\n            ctx.shadowOffsetY = 2 * this.ds.scale;\n            ctx.shadowBlur = 3 * this.ds.scale;\n        } else {\n            ctx.shadowColor = \"transparent\";\n        }\n\n        //custom draw collapsed method (draw after shadows because they are affected)\n        if (\n            node.flags.collapsed &&\n            node.onDrawCollapsed &&\n            node.onDrawCollapsed(ctx, this) == true\n        ) {\n            return;\n        }\n\n        //clip if required (mask)\n        var shape = node._shape || LiteGraph.BOX_SHAPE;\n        var size = temp_vec2;\n        temp_vec2.set(node.size);\n        var horizontal = node.horizontal; // || node.flags.horizontal;\n\n        if (node.flags.collapsed) {\n            ctx.font = this.inner_text_font;\n            var title = node.getTitle ? node.getTitle() : node.title;\n            if (title != null) {\n                node._collapsed_width = Math.min(\n                    node.size[0],\n                    ctx.measureText(title).width +\n                        LiteGraph.NODE_TITLE_HEIGHT * 2\n                ); //LiteGraph.NODE_COLLAPSED_WIDTH;\n                size[0] = node._collapsed_width;\n                size[1] = 0;\n            }\n        }\n\n        if (node.clip_area) {\n            //Start clipping\n            ctx.save();\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE) {\n                ctx.rect(0, 0, size[0], size[1]);\n            } else if (shape == LiteGraph.ROUND_SHAPE) {\n                ctx.roundRect(0, 0, size[0], size[1], [10]);\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.clip();\n        }\n\n        //draw shape\n        if (node.has_errors) {\n            bgcolor = \"red\";\n        }\n        this.drawNodeShape(\n            node,\n            ctx,\n            size,\n            color,\n            bgcolor,\n            node.is_selected,\n            node.mouseOver\n        );\n        ctx.shadowColor = \"transparent\";\n\n        //draw foreground\n        if (node.onDrawForeground) {\n            node.onDrawForeground(ctx, this, this.canvas);\n        }\n\n        //connection slots\n        ctx.textAlign = horizontal ? \"center\" : \"left\";\n        ctx.font = this.inner_text_font;\n\n        var render_text = !low_quality;\n\n        var out_slot = this.connecting_output;\n        var in_slot = this.connecting_input;\n        ctx.lineWidth = 1;\n\n        var max_y = 0;\n        var slot_pos = new Float32Array(2); //to reuse\n\n        //render inputs and outputs\n        if (!node.flags.collapsed) {\n            //input connection slots\n            if (node.inputs) {\n                for (var i = 0; i < node.inputs.length; i++) {\n                    var slot = node.inputs[i];\n                    \n                    var slot_type = slot.type;\n                    var slot_shape = slot.shape;\n                    \n                    ctx.globalAlpha = editor_alpha;\n                    //change opacity of incompatible slots when dragging a connection\n                    if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) {\n                        ctx.globalAlpha = 0.4 * editor_alpha;\n                    }\n\n                    ctx.fillStyle =\n                        slot.link != null\n                            ? slot.color_on ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.input_on\n                            : slot.color_off ||\n                              this.default_connection_color_byTypeOff[slot_type] ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.input_off;\n\n                    var pos = node.getConnectionPos(true, i, slot_pos);\n                    pos[0] -= node.pos[0];\n                    pos[1] -= node.pos[1];\n                    if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) {\n                        max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n                    }\n\n                    ctx.beginPath();\n\n\t\t\t\t\tif (slot_type == \"array\"){\n                        slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead?\n                    }\n                    \n                    var doStroke = true;\n                    \n                    if (\n                        slot.type === LiteGraph.EVENT ||\n                        slot.shape === LiteGraph.BOX_SHAPE\n                    ) {\n                        if (horizontal) {\n                            ctx.rect(\n                                pos[0] - 5 + 0.5,\n                                pos[1] - 8 + 0.5,\n                                10,\n                                14\n                            );\n                        } else {\n                            ctx.rect(\n                                pos[0] - 6 + 0.5,\n                                pos[1] - 5 + 0.5,\n                                14,\n                                10\n                            );\n                        }\n                    } else if (slot_shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(pos[0] + 8, pos[1] + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else if (slot_shape === LiteGraph.GRID_SHAPE) {\n                        ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2);\n                        doStroke = false;\n                    } else {\n\t\t\t\t\t\tif(low_quality)\n\t                        ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster\n\t\t\t\t\t\telse\n\t                        ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);\n                    }\n                    ctx.fill();\n\n                    //render name\n                    if (render_text) {\n                        var text = slot.label != null ? slot.label : slot.name;\n                        if (text) {\n                            ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;\n                            if (horizontal || slot.dir == LiteGraph.UP) {\n                                ctx.fillText(text, pos[0], pos[1] - 10);\n                            } else {\n                                ctx.fillText(text, pos[0] + 10, pos[1] + 5);\n                            }\n                        }\n                    }\n                }\n            }\n\n            //output connection slots\n\n            ctx.textAlign = horizontal ? \"center\" : \"right\";\n            ctx.strokeStyle = \"black\";\n            if (node.outputs) {\n                for (var i = 0; i < node.outputs.length; i++) {\n                    var slot = node.outputs[i];\n                    \n                    var slot_type = slot.type;\n                    var slot_shape = slot.shape;\n                    \n                    //change opacity of incompatible slots when dragging a connection\n                    if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) {\n                        ctx.globalAlpha = 0.4 * editor_alpha;\n                    }\n                    \n                    var pos = node.getConnectionPos(false, i, slot_pos);\n                    pos[0] -= node.pos[0];\n                    pos[1] -= node.pos[1];\n                    if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) {\n                        max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n                    }\n\n                    ctx.fillStyle =\n                        slot.links && slot.links.length\n                            ? slot.color_on ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.output_on\n                            : slot.color_off ||\n                              this.default_connection_color_byTypeOff[slot_type] ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.output_off;\n                    ctx.beginPath();\n                    //ctx.rect( node.size[0] - 14,i*14,10,10);\n\n\t\t\t\t\tif (slot_type == \"array\"){\n                        slot_shape = LiteGraph.GRID_SHAPE;\n                    }\n                    \n                    var doStroke = true;\n                    \n                    if (\n                        slot_type === LiteGraph.EVENT ||\n                        slot_shape === LiteGraph.BOX_SHAPE\n                    ) {\n                        if (horizontal) {\n                            ctx.rect(\n                                pos[0] - 5 + 0.5,\n                                pos[1] - 8 + 0.5,\n                                10,\n                                14\n                            );\n                        } else {\n                            ctx.rect(\n                                pos[0] - 6 + 0.5,\n                                pos[1] - 5 + 0.5,\n                                14,\n                                10\n                            );\n                        }\n                    } else if (slot_shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(pos[0] + 8, pos[1] + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);\n                        ctx.closePath();\n                    }  else if (slot_shape === LiteGraph.GRID_SHAPE) {\n                        ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2);\n                        doStroke = false;\n                    } else {\n\t\t\t\t\t\tif(low_quality)\n\t                        ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 );\n\t\t\t\t\t\telse\n\t                        ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);\n                    }\n\n                    //trigger\n                    //if(slot.node_id != null && slot.slot == -1)\n                    //\tctx.fillStyle = \"#F85\";\n\n                    //if(slot.links != null && slot.links.length)\n                    ctx.fill();\n\t\t\t\t\tif(!low_quality && doStroke)\n\t                    ctx.stroke();\n\n                    //render output name\n                    if (render_text) {\n                        var text = slot.label != null ? slot.label : slot.name;\n                        if (text) {\n                            ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;\n                            if (horizontal || slot.dir == LiteGraph.DOWN) {\n                                ctx.fillText(text, pos[0], pos[1] - 8);\n                            } else {\n                                ctx.fillText(text, pos[0] - 10, pos[1] + 5);\n                            }\n                        }\n                    }\n                }\n            }\n\n            ctx.textAlign = \"left\";\n            ctx.globalAlpha = 1;\n\n            if (node.widgets) {\n\t\t\t\tvar widgets_y = max_y;\n                if (horizontal || node.widgets_up) {\n                    widgets_y = 2;\n                }\n\t\t\t\tif( node.widgets_start_y != null )\n                    widgets_y = node.widgets_start_y;\n                this.drawNodeWidgets(\n                    node,\n                    widgets_y,\n                    ctx,\n                    this.node_widget && this.node_widget[0] == node\n                        ? this.node_widget[1]\n                        : null\n                );\n            }\n        } else if (this.render_collapsed_slots) {\n            //if collapsed\n            var input_slot = null;\n            var output_slot = null;\n\n            //get first connected slot to render\n            if (node.inputs) {\n                for (var i = 0; i < node.inputs.length; i++) {\n                    var slot = node.inputs[i];\n                    if (slot.link == null) {\n                        continue;\n                    }\n                    input_slot = slot;\n                    break;\n                }\n            }\n            if (node.outputs) {\n                for (var i = 0; i < node.outputs.length; i++) {\n                    var slot = node.outputs[i];\n                    if (!slot.links || !slot.links.length) {\n                        continue;\n                    }\n                    output_slot = slot;\n                }\n            }\n\n            if (input_slot) {\n                var x = 0;\n                var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center\n                if (horizontal) {\n                    x = node._collapsed_width * 0.5;\n                    y = -LiteGraph.NODE_TITLE_HEIGHT;\n                }\n                ctx.fillStyle = \"#686\";\n                ctx.beginPath();\n                if (\n                    slot.type === LiteGraph.EVENT ||\n                    slot.shape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(x - 7 + 0.5, y - 4, 14, 8);\n                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(x + 8, y);\n                    ctx.lineTo(x + -4, y - 4);\n                    ctx.lineTo(x + -4, y + 4);\n                    ctx.closePath();\n                } else {\n                    ctx.arc(x, y, 4, 0, Math.PI * 2);\n                }\n                ctx.fill();\n            }\n\n            if (output_slot) {\n                var x = node._collapsed_width;\n                var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center\n                if (horizontal) {\n                    x = node._collapsed_width * 0.5;\n                    y = 0;\n                }\n                ctx.fillStyle = \"#686\";\n                ctx.strokeStyle = \"black\";\n                ctx.beginPath();\n                if (\n                    slot.type === LiteGraph.EVENT ||\n                    slot.shape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(x - 7 + 0.5, y - 4, 14, 8);\n                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(x + 6, y);\n                    ctx.lineTo(x - 6, y - 4);\n                    ctx.lineTo(x - 6, y + 4);\n                    ctx.closePath();\n                } else {\n                    ctx.arc(x, y, 4, 0, Math.PI * 2);\n                }\n                ctx.fill();\n                //ctx.stroke();\n            }\n        }\n\n        if (node.clip_area) {\n            ctx.restore();\n        }\n\n        ctx.globalAlpha = 1.0;\n    };\n\n\t//used by this.over_link_center\n\tLGraphCanvas.prototype.drawLinkTooltip = function( ctx, link )\n\t{\n\t\tvar pos = link._pos;\n\t\tctx.fillStyle = \"black\";\n\t\tctx.beginPath();\n\t\tctx.arc( pos[0], pos[1], 3, 0, Math.PI * 2 );\n\t\tctx.fill();\n\n\t\tif(link.data == null)\n\t\t\treturn;\n\n\t\tif(this.onDrawLinkTooltip)\n\t\t\tif( this.onDrawLinkTooltip(ctx,link,this) == true )\n\t\t\t\treturn;\n\n\t\tvar data = link.data;\n\t\tvar text = null;\n\n\t\tif( data.constructor === Number )\n\t\t\ttext = data.toFixed(2);\n\t\telse if( data.constructor === String )\n\t\t\ttext = \"\\\"\" + data + \"\\\"\";\n\t\telse if( data.constructor === Boolean )\n\t\t\ttext = String(data);\n\t\telse if (data.toToolTip)\n\t\t\ttext = data.toToolTip();\n\t\telse\n\t\t\ttext = \"[\" + data.constructor.name + \"]\";\n\n\t\tif(text == null)\n\t\t\treturn;\n\t\ttext = text.substr(0,30); //avoid weird\n\n\t\tctx.font = \"14px Courier New\";\n\t\tvar info = ctx.measureText(text);\n\t\tvar w = info.width + 20;\n\t\tvar h = 24;\n\t\tctx.shadowColor = \"black\";\n\t\tctx.shadowOffsetX = 2;\n\t\tctx.shadowOffsetY = 2;\n\t\tctx.shadowBlur = 3;\n\t\tctx.fillStyle = \"#454\";\n\t\tctx.beginPath();\n\t\tctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]);\n\t\tctx.moveTo( pos[0] - 10, pos[1] - 15 );\n\t\tctx.lineTo( pos[0] + 10, pos[1] - 15 );\n\t\tctx.lineTo( pos[0], pos[1] - 5 );\n\t\tctx.fill();\n        ctx.shadowColor = \"transparent\";\n\t\tctx.textAlign = \"center\";\n\t\tctx.fillStyle = \"#CEC\";\n\t\tctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3);\n\t}\n\n    /**\n     * draws the shape of the given node in the canvas\n     * @method drawNodeShape\n     **/\n    var tmp_area = new Float32Array(4);\n\n    LGraphCanvas.prototype.drawNodeShape = function(\n        node,\n        ctx,\n        size,\n        fgcolor,\n        bgcolor,\n        selected,\n        mouse_over\n    ) {\n        //bg rect\n        ctx.strokeStyle = fgcolor;\n        ctx.fillStyle = bgcolor;\n\n        var title_height = LiteGraph.NODE_TITLE_HEIGHT;\n        var low_quality = this.ds.scale < 0.5;\n\n        //render node area depending on shape\n        var shape =\n            node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;\n\n        var title_mode = node.constructor.title_mode;\n\n        var render_title = true;\n        if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) {\n            render_title = false;\n        } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) {\n            render_title = true;\n        }\n\n        var area = tmp_area;\n        area[0] = 0; //x\n        area[1] = render_title ? -title_height : 0; //y\n        area[2] = size[0] + 1; //w\n        area[3] = render_title ? size[1] + title_height : size[1]; //h\n\n        var old_alpha = ctx.globalAlpha;\n\n        //full node shape\n        //if(node.flags.collapsed)\n        {\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE || low_quality) {\n                ctx.fillRect(area[0], area[1], area[2], area[3]);\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                shape == LiteGraph.CARD_SHAPE\n            ) {\n                ctx.roundRect(\n                    area[0],\n                    area[1],\n                    area[2],\n                    area[3],\n                    shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] \n                );\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.fill();\n\n\t\t\t//separator\n\t\t\tif(!node.flags.collapsed && render_title)\n\t\t\t{\n\t\t\t\tctx.shadowColor = \"transparent\";\n\t\t\t\tctx.fillStyle = \"rgba(0,0,0,0.2)\";\n\t\t\t\tctx.fillRect(0, -1, area[2], 2);\n\t\t\t}\n        }\n        ctx.shadowColor = \"transparent\";\n\n        if (node.onDrawBackground) {\n            node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse );\n        }\n\n        //title bg (remember, it is rendered ABOVE the node)\n        if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) {\n            //title bar\n            if (node.onDrawTitleBar) {\n                node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor );\n            } else if (\n                title_mode != LiteGraph.TRANSPARENT_TITLE &&\n                (node.constructor.title_color || this.render_title_colored)\n            ) {\n                var title_color = node.constructor.title_color || fgcolor;\n\n                if (node.flags.collapsed) {\n                    ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;\n                }\n\n                //* gradient test\n                if (this.use_gradients) {\n                    var grad = LGraphCanvas.gradients[title_color];\n                    if (!grad) {\n                        grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0);\n                        grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException\n                        grad.addColorStop(1, \"#000\");\n                    }\n                    ctx.fillStyle = grad;\n                } else {\n                    ctx.fillStyle = title_color;\n                }\n\n                //ctx.globalAlpha = 0.5 * old_alpha;\n                ctx.beginPath();\n                if (shape == LiteGraph.BOX_SHAPE || low_quality) {\n                    ctx.rect(0, -title_height, size[0] + 1, title_height);\n                } else if (  shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) {\n                    ctx.roundRect(\n                        0,\n                        -title_height,\n                        size[0] + 1,\n                        title_height,\n                        node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0]\n                    );\n                }\n                ctx.fill();\n                ctx.shadowColor = \"transparent\";\n            }\n\n            var colState = false;\n            if (LiteGraph.node_box_coloured_by_mode){\n                if(LiteGraph.NODE_MODES_COLORS[node.mode]){\n                    colState = LiteGraph.NODE_MODES_COLORS[node.mode];\n                }\n            }\n            if (LiteGraph.node_box_coloured_when_on){\n                colState = node.action_triggered ? \"#FFF\" : (node.execute_triggered ? \"#AAA\" : colState);\n            }\n            \n            //title box\n            var box_size = 10;\n            if (node.onDrawTitleBox) {\n                node.onDrawTitleBox(ctx, title_height, size, this.ds.scale);\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                shape == LiteGraph.CIRCLE_SHAPE ||\n                shape == LiteGraph.CARD_SHAPE\n            ) {\n                if (low_quality) {\n                    ctx.fillStyle = \"black\";\n                    ctx.beginPath();\n                    ctx.arc(\n                        title_height * 0.5,\n                        title_height * -0.5,\n                        box_size * 0.5 + 1,\n                        0,\n                        Math.PI * 2\n                    );\n                    ctx.fill();\n                }\n                \n                ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR;\n\t\t\t\tif(low_quality)\n\t\t\t\t\tctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size  );\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.arc(\n\t\t\t\t\t\ttitle_height * 0.5,\n\t\t\t\t\t\ttitle_height * -0.5,\n\t\t\t\t\t\tbox_size * 0.5,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tMath.PI * 2\n\t\t\t\t\t);\n\t\t\t\t\tctx.fill();\n\t\t\t\t}\n            } else {\n                if (low_quality) {\n                    ctx.fillStyle = \"black\";\n                    ctx.fillRect(\n                        (title_height - box_size) * 0.5 - 1,\n                        (title_height + box_size) * -0.5 - 1,\n                        box_size + 2,\n                        box_size + 2\n                    );\n                }\n                ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR;\n                ctx.fillRect(\n                    (title_height - box_size) * 0.5,\n                    (title_height + box_size) * -0.5,\n                    box_size,\n                    box_size\n                );\n            }\n            ctx.globalAlpha = old_alpha;\n\n            //title text\n            if (node.onDrawTitleText) {\n                node.onDrawTitleText(\n                    ctx,\n                    title_height,\n                    size,\n                    this.ds.scale,\n                    this.title_text_font,\n                    selected\n                );\n            }\n            if (!low_quality) {\n                ctx.font = this.title_text_font;\n                var title = String(node.getTitle());\n                if (title) {\n                    if (selected) {\n                        ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR;\n                    } else {\n                        ctx.fillStyle =\n                            node.constructor.title_text_color ||\n                            this.node_title_color;\n                    }\n                    if (node.flags.collapsed) {\n                        ctx.textAlign = \"left\";\n                        var measure = ctx.measureText(title);\n                        ctx.fillText(\n                            title.substr(0,20), //avoid urls too long\n                            title_height,// + measure.width * 0.5,\n                            LiteGraph.NODE_TITLE_TEXT_Y - title_height\n                        );\n                        ctx.textAlign = \"left\";\n                    } else {\n                        ctx.textAlign = \"left\";\n                        ctx.fillText(\n                            title,\n                            title_height,\n                            LiteGraph.NODE_TITLE_TEXT_Y - title_height\n                        );\n                    }\n                }\n            }\n\n\t\t\t//subgraph box\n\t\t\tif (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) {\n\t\t\t\tvar w = LiteGraph.NODE_TITLE_HEIGHT;\n\t\t\t\tvar x = node.size[0] - w;\n\t\t\t\tvar over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 );\n\t\t\t\tctx.fillStyle = over ? \"#888\" : \"#555\";\n\t\t\t\tif( shape == LiteGraph.BOX_SHAPE || low_quality)\n\t\t\t\t\tctx.fillRect(x+2, -w+2, w-4, w-4);\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.roundRect(x+2, -w+2, w-4, w-4,[4]);\n\t\t\t\t\tctx.fill();\n\t\t\t\t}\n\t\t\t\tctx.fillStyle = \"#333\";\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.moveTo(x + w * 0.2, -w * 0.6);\n\t\t\t\tctx.lineTo(x + w * 0.8, -w * 0.6);\n\t\t\t\tctx.lineTo(x + w * 0.5, -w * 0.3);\n\t\t\t\tctx.fill();\n\t\t\t}\n\n\t\t\t//custom title render\n            if (node.onDrawTitle) {\n                node.onDrawTitle(ctx);\n            }\n        }\n\n        //render selection marker\n        if (selected) {\n            if (node.onBounding) {\n                node.onBounding(area);\n            }\n\n            if (title_mode == LiteGraph.TRANSPARENT_TITLE) {\n                area[1] -= title_height;\n                area[3] += title_height;\n            }\n            ctx.lineWidth = 1;\n            ctx.globalAlpha = 0.8;\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE) {\n                ctx.rect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3]\n                );\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed)\n            ) {\n                ctx.roundRect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3],\n                    [this.round_radius * 2]\n                );\n            } else if (shape == LiteGraph.CARD_SHAPE) {\n                ctx.roundRect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3],\n                    [this.round_radius * 2,2,this.round_radius * 2,2]\n                );\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5 + 6,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR;\n            ctx.stroke();\n            ctx.strokeStyle = fgcolor;\n            ctx.globalAlpha = 1;\n        }\n        \n        // these counter helps in conditioning drawing based on if the node has been executed or an action occurred\n        if (node.execute_triggered>0) node.execute_triggered--;\n        if (node.action_triggered>0) node.action_triggered--;\n    };\n\n    var margin_area = new Float32Array(4);\n    var link_bounding = new Float32Array(4);\n    var tempA = new Float32Array(2);\n    var tempB = new Float32Array(2);\n\n    /**\n     * draws every connection visible in the canvas\n     * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time\n     * @method drawConnections\n     **/\n    LGraphCanvas.prototype.drawConnections = function(ctx) {\n        var now = LiteGraph.getTime();\n        var visible_area = this.visible_area;\n        margin_area[0] = visible_area[0] - 20;\n        margin_area[1] = visible_area[1] - 20;\n        margin_area[2] = visible_area[2] + 40;\n        margin_area[3] = visible_area[3] + 40;\n\n        //draw connections\n        ctx.lineWidth = this.connections_width;\n\n        ctx.fillStyle = \"#AAA\";\n        ctx.strokeStyle = \"#AAA\";\n        ctx.globalAlpha = this.editor_alpha;\n        //for every node\n        var nodes = this.graph._nodes;\n        for (var n = 0, l = nodes.length; n < l; ++n) {\n            var node = nodes[n];\n            //for every input (we render just inputs because it is easier as every slot can only have one input)\n            if (!node.inputs || !node.inputs.length) {\n                continue;\n            }\n\n            for (var i = 0; i < node.inputs.length; ++i) {\n                var input = node.inputs[i];\n                if (!input || input.link == null) {\n                    continue;\n                }\n                var link_id = input.link;\n                var link = this.graph.links[link_id];\n                if (!link) {\n                    continue;\n                }\n\n                //find link info\n                var start_node = this.graph.getNodeById(link.origin_id);\n                if (start_node == null) {\n                    continue;\n                }\n                var start_node_slot = link.origin_slot;\n                var start_node_slotpos = null;\n                if (start_node_slot == -1) {\n                    start_node_slotpos = [\n                        start_node.pos[0] + 10,\n                        start_node.pos[1] + 10\n                    ];\n                } else {\n                    start_node_slotpos = start_node.getConnectionPos(\n                        false,\n                        start_node_slot,\n                        tempA\n                    );\n                }\n                var end_node_slotpos = node.getConnectionPos(true, i, tempB);\n\n                //compute link bounding\n                link_bounding[0] = start_node_slotpos[0];\n                link_bounding[1] = start_node_slotpos[1];\n                link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0];\n                link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1];\n                if (link_bounding[2] < 0) {\n                    link_bounding[0] += link_bounding[2];\n                    link_bounding[2] = Math.abs(link_bounding[2]);\n                }\n                if (link_bounding[3] < 0) {\n                    link_bounding[1] += link_bounding[3];\n                    link_bounding[3] = Math.abs(link_bounding[3]);\n                }\n\n                //skip links outside of the visible area of the canvas\n                if (!overlapBounding(link_bounding, margin_area)) {\n                    continue;\n                }\n\n                var start_slot = start_node.outputs[start_node_slot];\n                var end_slot = node.inputs[i];\n                if (!start_slot || !end_slot) {\n                    continue;\n                }\n                var start_dir =\n                    start_slot.dir ||\n                    (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT);\n                var end_dir =\n                    end_slot.dir ||\n                    (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT);\n\n                this.renderLink(\n                    ctx,\n                    start_node_slotpos,\n                    end_node_slotpos,\n                    link,\n                    false,\n                    0,\n                    null,\n                    start_dir,\n                    end_dir\n                );\n\n                //event triggered rendered on top\n                if (link && link._last_time && now - link._last_time < 1000) {\n                    var f = 2.0 - (now - link._last_time) * 0.002;\n                    var tmp = ctx.globalAlpha;\n                    ctx.globalAlpha = tmp * f;\n                    this.renderLink(\n                        ctx,\n                        start_node_slotpos,\n                        end_node_slotpos,\n                        link,\n                        true,\n                        f,\n                        \"white\",\n                        start_dir,\n                        end_dir\n                    );\n                    ctx.globalAlpha = tmp;\n                }\n            }\n        }\n        ctx.globalAlpha = 1;\n    };\n\n    /**\n     * draws a link between two points\n     * @method renderLink\n     * @param {vec2} a start pos\n     * @param {vec2} b end pos\n     * @param {Object} link the link object with all the link info\n     * @param {boolean} skip_border ignore the shadow of the link\n     * @param {boolean} flow show flow animation (for events)\n     * @param {string} color the color for the link\n     * @param {number} start_dir the direction enum\n     * @param {number} end_dir the direction enum\n     * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb)\n     **/\n    LGraphCanvas.prototype.renderLink = function(\n        ctx,\n        a,\n        b,\n        link,\n        skip_border,\n        flow,\n        color,\n        start_dir,\n        end_dir,\n        num_sublines\n    ) {\n        if (link) {\n            this.visible_links.push(link);\n        }\n\n        //choose color\n        if (!color && link) {\n            color = link.color || LGraphCanvas.link_type_colors[link.type];\n        }\n        if (!color) {\n            color = this.default_link_color;\n        }\n        if (link != null && this.highlighted_links[link.id]) {\n            color = \"#FFF\";\n        }\n\n        start_dir = start_dir || LiteGraph.RIGHT;\n        end_dir = end_dir || LiteGraph.LEFT;\n\n        var dist = distance(a, b);\n\n        if (this.render_connections_border && this.ds.scale > 0.6) {\n            ctx.lineWidth = this.connections_width + 4;\n        }\n        ctx.lineJoin = \"round\";\n        num_sublines = num_sublines || 1;\n        if (num_sublines > 1) {\n            ctx.lineWidth = 0.5;\n        }\n\n        //begin line shape\n        ctx.beginPath();\n        for (var i = 0; i < num_sublines; i += 1) {\n            var offsety = (i - (num_sublines - 1) * 0.5) * 5;\n\n            if (this.links_render_mode == LiteGraph.SPLINE_LINK) {\n                ctx.moveTo(a[0], a[1] + offsety);\n                var start_offset_x = 0;\n                var start_offset_y = 0;\n                var end_offset_x = 0;\n                var end_offset_y = 0;\n                switch (start_dir) {\n                    case LiteGraph.LEFT:\n                        start_offset_x = dist * -0.25;\n                        break;\n                    case LiteGraph.RIGHT:\n                        start_offset_x = dist * 0.25;\n                        break;\n                    case LiteGraph.UP:\n                        start_offset_y = dist * -0.25;\n                        break;\n                    case LiteGraph.DOWN:\n                        start_offset_y = dist * 0.25;\n                        break;\n                }\n                switch (end_dir) {\n                    case LiteGraph.LEFT:\n                        end_offset_x = dist * -0.25;\n                        break;\n                    case LiteGraph.RIGHT:\n                        end_offset_x = dist * 0.25;\n                        break;\n                    case LiteGraph.UP:\n                        end_offset_y = dist * -0.25;\n                        break;\n                    case LiteGraph.DOWN:\n                        end_offset_y = dist * 0.25;\n                        break;\n                }\n                ctx.bezierCurveTo(\n                    a[0] + start_offset_x,\n                    a[1] + start_offset_y + offsety,\n                    b[0] + end_offset_x,\n                    b[1] + end_offset_y + offsety,\n                    b[0],\n                    b[1] + offsety\n                );\n            } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) {\n                ctx.moveTo(a[0], a[1] + offsety);\n                var start_offset_x = 0;\n                var start_offset_y = 0;\n                var end_offset_x = 0;\n                var end_offset_y = 0;\n                switch (start_dir) {\n                    case LiteGraph.LEFT:\n                        start_offset_x = -1;\n                        break;\n                    case LiteGraph.RIGHT:\n                        start_offset_x = 1;\n                        break;\n                    case LiteGraph.UP:\n                        start_offset_y = -1;\n                        break;\n                    case LiteGraph.DOWN:\n                        start_offset_y = 1;\n                        break;\n                }\n                switch (end_dir) {\n                    case LiteGraph.LEFT:\n                        end_offset_x = -1;\n                        break;\n                    case LiteGraph.RIGHT:\n                        end_offset_x = 1;\n                        break;\n                    case LiteGraph.UP:\n                        end_offset_y = -1;\n                        break;\n                    case LiteGraph.DOWN:\n                        end_offset_y = 1;\n                        break;\n                }\n                var l = 15;\n                ctx.lineTo(\n                    a[0] + start_offset_x * l,\n                    a[1] + start_offset_y * l + offsety\n                );\n                ctx.lineTo(\n                    b[0] + end_offset_x * l,\n                    b[1] + end_offset_y * l + offsety\n                );\n                ctx.lineTo(b[0], b[1] + offsety);\n            } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) {\n                ctx.moveTo(a[0], a[1]);\n                var start_x = a[0];\n                var start_y = a[1];\n                var end_x = b[0];\n                var end_y = b[1];\n                if (start_dir == LiteGraph.RIGHT) {\n                    start_x += 10;\n                } else {\n                    start_y += 10;\n                }\n                if (end_dir == LiteGraph.LEFT) {\n                    end_x -= 10;\n                } else {\n                    end_y -= 10;\n                }\n                ctx.lineTo(start_x, start_y);\n                ctx.lineTo((start_x + end_x) * 0.5, start_y);\n                ctx.lineTo((start_x + end_x) * 0.5, end_y);\n                ctx.lineTo(end_x, end_y);\n                ctx.lineTo(b[0], b[1]);\n            } else {\n                return;\n            } //unknown\n        }\n\n        //rendering the outline of the connection can be a little bit slow\n        if (\n            this.render_connections_border &&\n            this.ds.scale > 0.6 &&\n            !skip_border\n        ) {\n            ctx.strokeStyle = \"rgba(0,0,0,0.5)\";\n            ctx.stroke();\n        }\n\n        ctx.lineWidth = this.connections_width;\n        ctx.fillStyle = ctx.strokeStyle = color;\n        ctx.stroke();\n        //end line shape\n\n        var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir);\n        if (link && link._pos) {\n            link._pos[0] = pos[0];\n            link._pos[1] = pos[1];\n        }\n\n        //render arrow in the middle\n        if (\n            this.ds.scale >= 0.6 &&\n            this.highquality_render &&\n            end_dir != LiteGraph.CENTER\n        ) {\n            //render arrow\n            if (this.render_connection_arrows) {\n                //compute two points in the connection\n                var posA = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.25,\n                    start_dir,\n                    end_dir\n                );\n                var posB = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.26,\n                    start_dir,\n                    end_dir\n                );\n                var posC = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.75,\n                    start_dir,\n                    end_dir\n                );\n                var posD = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.76,\n                    start_dir,\n                    end_dir\n                );\n\n                //compute the angle between them so the arrow points in the right direction\n                var angleA = 0;\n                var angleB = 0;\n                if (this.render_curved_connections) {\n                    angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]);\n                    angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]);\n                } else {\n                    angleB = angleA = b[1] > a[1] ? 0 : Math.PI;\n                }\n\n                //render arrow\n                ctx.save();\n                ctx.translate(posA[0], posA[1]);\n                ctx.rotate(angleA);\n                ctx.beginPath();\n                ctx.moveTo(-5, -3);\n                ctx.lineTo(0, +7);\n                ctx.lineTo(+5, -3);\n                ctx.fill();\n                ctx.restore();\n                ctx.save();\n                ctx.translate(posC[0], posC[1]);\n                ctx.rotate(angleB);\n                ctx.beginPath();\n                ctx.moveTo(-5, -3);\n                ctx.lineTo(0, +7);\n                ctx.lineTo(+5, -3);\n                ctx.fill();\n                ctx.restore();\n            }\n\n            //circle\n            ctx.beginPath();\n            ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2);\n            ctx.fill();\n        }\n\n        //render flowing points\n        if (flow) {\n            ctx.fillStyle = color;\n            for (var i = 0; i < 5; ++i) {\n                var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1;\n                var pos = this.computeConnectionPoint(\n                    a,\n                    b,\n                    f,\n                    start_dir,\n                    end_dir\n                );\n                ctx.beginPath();\n                ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI);\n                ctx.fill();\n            }\n        }\n    };\n\n    //returns the link center point based on curvature\n    LGraphCanvas.prototype.computeConnectionPoint = function(\n        a,\n        b,\n        t,\n        start_dir,\n        end_dir\n    ) {\n        start_dir = start_dir || LiteGraph.RIGHT;\n        end_dir = end_dir || LiteGraph.LEFT;\n\n        var dist = distance(a, b);\n        var p0 = a;\n        var p1 = [a[0], a[1]];\n        var p2 = [b[0], b[1]];\n        var p3 = b;\n\n        switch (start_dir) {\n            case LiteGraph.LEFT:\n                p1[0] += dist * -0.25;\n                break;\n            case LiteGraph.RIGHT:\n                p1[0] += dist * 0.25;\n                break;\n            case LiteGraph.UP:\n                p1[1] += dist * -0.25;\n                break;\n            case LiteGraph.DOWN:\n                p1[1] += dist * 0.25;\n                break;\n        }\n        switch (end_dir) {\n            case LiteGraph.LEFT:\n                p2[0] += dist * -0.25;\n                break;\n            case LiteGraph.RIGHT:\n                p2[0] += dist * 0.25;\n                break;\n            case LiteGraph.UP:\n                p2[1] += dist * -0.25;\n                break;\n            case LiteGraph.DOWN:\n                p2[1] += dist * 0.25;\n                break;\n        }\n\n        var c1 = (1 - t) * (1 - t) * (1 - t);\n        var c2 = 3 * ((1 - t) * (1 - t)) * t;\n        var c3 = 3 * (1 - t) * (t * t);\n        var c4 = t * t * t;\n\n        var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0];\n        var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1];\n        return [x, y];\n    };\n\n    LGraphCanvas.prototype.drawExecutionOrder = function(ctx) {\n        ctx.shadowColor = \"transparent\";\n        ctx.globalAlpha = 0.25;\n\n        ctx.textAlign = \"center\";\n        ctx.strokeStyle = \"white\";\n        ctx.globalAlpha = 0.75;\n\n        var visible_nodes = this.visible_nodes;\n        for (var i = 0; i < visible_nodes.length; ++i) {\n            var node = visible_nodes[i];\n            ctx.fillStyle = \"black\";\n            ctx.fillRect(\n                node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT,\n                node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT,\n                LiteGraph.NODE_TITLE_HEIGHT,\n                LiteGraph.NODE_TITLE_HEIGHT\n            );\n            if (node.order == 0) {\n                ctx.strokeRect(\n                    node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,\n                    node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,\n                    LiteGraph.NODE_TITLE_HEIGHT,\n                    LiteGraph.NODE_TITLE_HEIGHT\n                );\n            }\n            ctx.fillStyle = \"#FFF\";\n            ctx.fillText(\n                node.order,\n                node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5,\n                node.pos[1] - 6\n            );\n        }\n        ctx.globalAlpha = 1;\n    };\n\n    /**\n     * draws the widgets stored inside a node\n     * @method drawNodeWidgets\n     **/\n    LGraphCanvas.prototype.drawNodeWidgets = function(\n        node,\n        posY,\n        ctx,\n        active_widget\n    ) {\n        if (!node.widgets || !node.widgets.length) {\n            return 0;\n        }\n        var width = node.size[0];\n        var widgets = node.widgets;\n        posY += 2;\n        var H = LiteGraph.NODE_WIDGET_HEIGHT;\n        var show_text = this.ds.scale > 0.5;\n        ctx.save();\n        ctx.globalAlpha = this.editor_alpha;\n        var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR;\n        var background_color = LiteGraph.WIDGET_BGCOLOR;\n        var text_color = LiteGraph.WIDGET_TEXT_COLOR;\n\t\tvar secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;\n        var margin = 15;\n\n        for (var i = 0; i < widgets.length; ++i) {\n            var w = widgets[i];\n            var y = posY;\n            if (w.y) {\n                y = w.y;\n            }\n            w.last_y = y;\n            ctx.strokeStyle = outline_color;\n            ctx.fillStyle = \"#222\";\n            ctx.textAlign = \"left\";\n\t\t\t//ctx.lineWidth = 2;\n\t\t\tif(w.disabled)\n\t\t\t\tctx.globalAlpha *= 0.5;\n\t\t\tvar widget_width = w.width || width;\n\n            switch (w.type) {\n                case \"button\":\n                    if (w.clicked) {\n                        ctx.fillStyle = \"#AAA\";\n                        w.clicked = false;\n                        this.dirty_canvas = true;\n                    }\n                    ctx.fillRect(margin, y, widget_width - margin * 2, H);\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.strokeRect( margin, y, widget_width - margin * 2, H );\n                    if (show_text) {\n                        ctx.textAlign = \"center\";\n                        ctx.fillStyle = text_color;\n                        ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7);\n                    }\n                    break;\n                case \"toggle\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n                    if (show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]);\n\t\t\t\t\telse\n\t                    ctx.rect(margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.stroke();\n                    ctx.fillStyle = w.value ? \"#89A\" : \"#333\";\n                    ctx.beginPath();\n                    ctx.arc( widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2 );\n                    ctx.fill();\n                    if (show_text) {\n                        ctx.fillStyle = secondary_text_color;\n                        const label = w.label || w.name;    \n                        if (label != null) {\n                            ctx.fillText(label, margin * 2, y + H * 0.7);\n                        }\n                        ctx.fillStyle = w.value ? text_color : secondary_text_color;\n                        ctx.textAlign = \"right\";\n                        ctx.fillText(\n                            w.value\n                                ? w.options.on || \"true\"\n                                : w.options.off || \"false\",\n                            widget_width - 40,\n                            y + H * 0.7\n                        );\n                    }\n                    break;\n                case \"slider\":\n                    ctx.fillStyle = background_color;\n                    ctx.fillRect(margin, y, widget_width - margin * 2, H);\n                    var range = w.options.max - w.options.min;\n                    var nvalue = (w.value - w.options.min) / range;\n\t\t\t\t\tif(nvalue < 0.0) nvalue = 0.0;\n\t\t\t\t\tif(nvalue > 1.0) nvalue = 1.0;\n                    ctx.fillStyle = w.options.hasOwnProperty(\"slider_color\") ? w.options.slider_color : (active_widget == w ? \"#89A\" : \"#678\");\n                    ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H);\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.strokeRect(margin, y, widget_width - margin * 2, H);\n                    if (w.marker) {\n                        var marker_nvalue = (w.marker - w.options.min) / range;\n\t\t\t\t\t\tif(marker_nvalue < 0.0) marker_nvalue = 0.0;\n\t\t\t\t\t\tif(marker_nvalue > 1.0) marker_nvalue = 1.0;\n                        ctx.fillStyle = w.options.hasOwnProperty(\"marker_color\") ? w.options.marker_color : \"#AA9\";\n                        ctx.fillRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H );\n                    }\n                    if (show_text) {\n                        ctx.textAlign = \"center\";\n                        ctx.fillStyle = text_color;\n                        ctx.fillText(\n                            w.label || w.name + \"  \" + Number(w.value).toFixed(\n                                                            w.options.precision != null\n                                                                ? w.options.precision\n                                                                : 3\n                                                        ),\n                            widget_width * 0.5,\n                            y + H * 0.7\n                        );\n                    }\n                    break;\n                case \"number\":\n                case \"combo\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n\t\t\t\t\tif(show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5] );\n\t\t\t\t\telse\n\t                    ctx.rect(margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n                    if (show_text) {\n\t\t\t\t\t\tif(!w.disabled)\n\t\t                    ctx.stroke();\n                        ctx.fillStyle = text_color;\n\t\t\t\t\t\tif(!w.disabled)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\t\tctx.moveTo(margin + 16, y + 5);\n\t\t\t\t\t\t\tctx.lineTo(margin + 6, y + H * 0.5);\n\t\t\t\t\t\t\tctx.lineTo(margin + 16, y + H - 5);\n\t\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\t\tctx.moveTo(widget_width - margin - 16, y + 5);\n\t\t\t\t\t\t\tctx.lineTo(widget_width - margin - 6, y + H * 0.5);\n\t\t\t\t\t\t\tctx.lineTo(widget_width - margin - 16, y + H - 5);\n\t\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\t}\n                        ctx.fillStyle = secondary_text_color;\n                        ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7);\n                        ctx.fillStyle = text_color;\n                        ctx.textAlign = \"right\";\n                        if (w.type == \"number\") {\n                            ctx.fillText(\n                                Number(w.value).toFixed(\n                                    w.options.precision !== undefined\n                                        ? w.options.precision\n                                        : 3\n                                ),\n                                widget_width - margin * 2 - 20,\n                                y + H * 0.7\n                            );\n                        } else {\n\t\t\t\t\t\t\tvar v = w.value;\n\t\t\t\t\t\t\tif( w.options.values )\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tvar values = w.options.values;\n\t\t\t\t\t\t\t\tif( values.constructor === Function )\n\t\t\t\t\t\t\t\t\tvalues = values();\n\t\t\t\t\t\t\t\tif(values && values.constructor !== Array)\n\t\t\t\t\t\t\t\t\tv = values[ w.value ];\n\t\t\t\t\t\t\t}\n                            ctx.fillText(\n                                v,\n                                widget_width - margin * 2 - 20,\n                                y + H * 0.7\n                            );\n                        }\n                    }\n                    break;\n                case \"string\":\n                case \"text\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n                    if (show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]);\n\t\t\t\t\telse\n\t                    ctx.rect( margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n\t                if (show_text) {\n\t\t\t\t\t\tif(!w.disabled)\n\t\t\t\t\t\t\tctx.stroke();\n    \t\t\t\t\tctx.save();\n\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\tctx.rect(margin, y, widget_width - margin * 2, H);\n\t\t\t\t\t\tctx.clip();\n\n\t                    //ctx.stroke();\n                        ctx.fillStyle = secondary_text_color;\n                        const label = w.label || w.name;\t\n                        if (label != null) {\n                            ctx.fillText(label, margin * 2, y + H * 0.7);\n                        }\n                        ctx.fillStyle = text_color;\n                        ctx.textAlign = \"right\";\n                        ctx.fillText(String(w.value).substr(0,30), widget_width - margin * 2, y + H * 0.7); //30 chars max\n\t\t\t\t\t\tctx.restore();\n                    }\n                    break;\n                default:\n                    if (w.draw) {\n                        w.draw(ctx, node, widget_width, y, H);\n                    }\n                    break;\n            }\n            posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4;\n\t\t\tctx.globalAlpha = this.editor_alpha;\n\n        }\n        ctx.restore();\n\t\tctx.textAlign = \"left\";\n    };\n\n    /**\n     * process an event on widgets\n     * @method processNodeWidgets\n     **/\n    LGraphCanvas.prototype.processNodeWidgets = function(\n        node,\n        pos,\n        event,\n        active_widget\n    ) {\n        if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) {\n            return null;\n        }\n\n        var x = pos[0] - node.pos[0];\n        var y = pos[1] - node.pos[1];\n        var width = node.size[0];\n        var deltaX = event.deltaX || event.deltax || 0;\n        var that = this;\n        var ref_window = this.getCanvasWindow();\n\n        for (var i = 0; i < node.widgets.length; ++i) {\n            var w = node.widgets[i];\n\t\t\tif(!w || w.disabled)\n\t\t\t\tcontinue;\n\t\t\tvar widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT;\n\t\t\tvar widget_width = w.width || width;\n\t\t\t//outside\n\t\t\tif ( w != active_widget && \n\t\t\t\t(x < 6 || x > widget_width - 12 || y < w.last_y || y > w.last_y + widget_height || w.last_y === undefined) ) \n\t\t\t\tcontinue;\n\n\t\t\tvar old_value = w.value;\n\n            //if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) {\n\t\t\t//inside widget\n\t\t\tswitch (w.type) {\n\t\t\t\tcase \"button\":\n\t\t\t\t\tif (event.type === LiteGraph.pointerevents_method+\"down\") {\n                        if (w.callback) {\n                            setTimeout(function() {\n                                w.callback(w, that, node, pos, event);\n                            }, 20);\n                        }\n                        w.clicked = true;\n                        this.dirty_canvas = true;\n                    }\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"slider\":\n\t\t\t\t\tvar old_value = w.value;\n\t\t\t\t\tvar nvalue = clamp((x - 15) / (widget_width - 30), 0, 1);\n\t\t\t\t\tif(w.options.read_only) break;\n\t\t\t\t\tw.value = w.options.min + (w.options.max - w.options.min) * nvalue;\n\t\t\t\t\tif (old_value != w.value) {\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tinner_value_change(w, w.value);\n\t\t\t\t\t\t}, 20);\n\t\t\t\t\t}\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"number\":\n\t\t\t\tcase \"combo\":\n\t\t\t\t\tvar old_value = w.value;\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"move\" && w.type == \"number\") {\n                        if(deltaX)\n\t\t\t\t\t\t    w.value += deltaX * 0.1 * (w.options.step || 1);\n\t\t\t\t\t\tif ( w.options.min != null && w.value < w.options.min ) {\n\t\t\t\t\t\t\tw.value = w.options.min;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( w.options.max != null && w.value > w.options.max ) {\n\t\t\t\t\t\t\tw.value = w.options.max;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tvar values = w.options.values;\n\t\t\t\t\t\tif (values && values.constructor === Function) {\n\t\t\t\t\t\t\tvalues = w.options.values(w, node);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar values_list = null;\n\t\t\t\t\t\t\n\t\t\t\t\t\tif( w.type != \"number\")\n\t\t\t\t\t\t\tvalues_list = values.constructor === Array ? values : Object.keys(values);\n\n\t\t\t\t\t\tvar delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0;\n\t\t\t\t\t\tif (w.type == \"number\") {\n\t\t\t\t\t\t\tw.value += delta * 0.1 * (w.options.step || 1);\n\t\t\t\t\t\t\tif ( w.options.min != null && w.value < w.options.min ) {\n\t\t\t\t\t\t\t\tw.value = w.options.min;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ( w.options.max != null && w.value > w.options.max ) {\n\t\t\t\t\t\t\t\tw.value = w.options.max;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (delta) { //clicked in arrow, used for combos \n\t\t\t\t\t\t\tvar index = -1;\n\t\t\t\t\t\t\tthis.last_mouseclick = 0; //avoids dobl click event\n\t\t\t\t\t\t\tif(values.constructor === Object)\n\t\t\t\t\t\t\t\tindex = values_list.indexOf( String( w.value ) ) + delta;\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tindex = values_list.indexOf( w.value ) + delta;\n\t\t\t\t\t\t\tif (index >= values_list.length) {\n\t\t\t\t\t\t\t\tindex = values_list.length - 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (index < 0) {\n\t\t\t\t\t\t\t\tindex = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif( values.constructor === Array )\n\t\t\t\t\t\t\t\tw.value = values[index];\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tw.value = index;\n\t\t\t\t\t\t} else { //combo clicked \n\t\t\t\t\t\t\tvar text_values = values != values_list ? Object.values(values) : values;\n\t\t\t\t\t\t\tvar menu = new LiteGraph.ContextMenu(text_values, {\n\t\t\t\t\t\t\t\t\tscale: Math.max(1, this.ds.scale),\n\t\t\t\t\t\t\t\t\tevent: event,\n\t\t\t\t\t\t\t\t\tclassName: \"dark\",\n\t\t\t\t\t\t\t\t\tcallback: inner_clicked.bind(w)\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tref_window);\n\t\t\t\t\t\t\tfunction inner_clicked(v, option, event) {\n\t\t\t\t\t\t\t\tif(values != values_list)\n\t\t\t\t\t\t\t\t\tv = text_values.indexOf(v);\n\t\t\t\t\t\t\t\tthis.value = v;\n\t\t\t\t\t\t\t\tinner_value_change(this, v);\n\t\t\t\t\t\t\t\tthat.dirty_canvas = true;\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} //end mousedown\n\t\t\t\t\telse if(event.type == LiteGraph.pointerevents_method+\"up\" && w.type == \"number\")\n\t\t\t\t\t{\n\t\t\t\t\t\tvar delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0;\n\t\t\t\t\t\tif (event.click_time < 200 && delta == 0) {\n\t\t\t\t\t\t\tthis.prompt(\"Value\",w.value,function(v) {\n\t\t\t\t\t\t\t\t\t// check if v is a valid equation or a number\n\t\t\t\t\t\t\t\t\t  if (/^[0-9+\\-*/()\\s]+|\\d+\\.\\d+$/.test(v)) {\n\t\t\t\t\t\t\t\t\t\ttry {//solve the equation if possible\n\t\t\t\t\t\t\t\t\t    \t\tv = eval(v);\n\t\t\t\t\t\t\t\t\t\t} catch (e) { }\n\t\t\t\t\t\t\t\t\t}\t\n\t\t\t\t\t\t\t\t\tthis.value = Number(v);\n\t\t\t\t\t\t\t\t\tinner_value_change(this, this.value);\n\t\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\t\tevent);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif( old_value != w.value )\n\t\t\t\t\t\tsetTimeout(\n\t\t\t\t\t\t\tfunction() {\n\t\t\t\t\t\t\t\tinner_value_change(this, this.value);\n\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\t20\n\t\t\t\t\t\t);\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"toggle\":\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tw.value = !w.value;\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tinner_value_change(w, w.value);\n\t\t\t\t\t\t}, 20);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"string\":\n\t\t\t\tcase \"text\":\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tthis.prompt(\"Value\",w.value,function(v) {\n\t\t\t\t\t\t\t\tinner_value_change(this, v);\n\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\tevent,w.options ? w.options.multiline : false );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tif (w.mouse) {\n\t\t\t\t\t\tthis.dirty_canvas = w.mouse(event, [x, y], node);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t} //end switch\n\n\t\t\t//value changed\n\t\t\tif( old_value != w.value )\n\t\t\t{\n\t\t\t\tif(node.onWidgetChanged)\n\t\t\t\t\tnode.onWidgetChanged( w.name,w.value,old_value,w );\n                node.graph._version++;\n\t\t\t}\n\n\t\t\treturn w;\n        }//end for\n\n        function inner_value_change(widget, value) {\n            if(widget.type == \"number\"){\n                value = Number(value);\n            }\n            widget.value = value;\n            if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) {\n                node.setProperty( widget.options.property, value );\n            }\n            if (widget.callback) {\n                widget.callback(widget.value, that, node, pos, event);\n            }\n        }\n\n        return null;\n    };\n\n    /**\n     * draws every group area in the background\n     * @method drawGroups\n     **/\n    LGraphCanvas.prototype.drawGroups = function(canvas, ctx) {\n        if (!this.graph) {\n            return;\n        }\n\n        var groups = this.graph._groups;\n\n        ctx.save();\n        ctx.globalAlpha = 0.5 * this.editor_alpha;\n\n        for (var i = 0; i < groups.length; ++i) {\n            var group = groups[i];\n\n            if (!overlapBounding(this.visible_area, group._bounding)) {\n                continue;\n            } //out of the visible area\n\n            ctx.fillStyle = group.color || \"#335\";\n            ctx.strokeStyle = group.color || \"#335\";\n            var pos = group._pos;\n            var size = group._size;\n            ctx.globalAlpha = 0.25 * this.editor_alpha;\n            ctx.beginPath();\n            ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]);\n            ctx.fill();\n            ctx.globalAlpha = this.editor_alpha;\n            ctx.stroke();\n\n            ctx.beginPath();\n            ctx.moveTo(pos[0] + size[0], pos[1] + size[1]);\n            ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]);\n            ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10);\n            ctx.fill();\n\n            var font_size =\n                group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;\n            ctx.font = font_size + \"px Arial\";\n\t\t\tctx.textAlign = \"left\";\n            ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size);\n        }\n\n        ctx.restore();\n    };\n\n    LGraphCanvas.prototype.adjustNodesSize = function() {\n        var nodes = this.graph._nodes;\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].size = nodes[i].computeSize();\n        }\n        this.setDirty(true, true);\n    };\n\n    /**\n     * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode\n     * @method resize\n     **/\n    LGraphCanvas.prototype.resize = function(width, height) {\n        if (!width && !height) {\n            var parent = this.canvas.parentNode;\n            width = parent.offsetWidth;\n            height = parent.offsetHeight;\n        }\n\n        if (this.canvas.width == width && this.canvas.height == height) {\n            return;\n        }\n\n        this.canvas.width = width;\n        this.canvas.height = height;\n        this.bgcanvas.width = this.canvas.width;\n        this.bgcanvas.height = this.canvas.height;\n        this.setDirty(true, true);\n    };\n\n    /**\n     * switches to live mode (node shapes are not rendered, only the content)\n     * this feature was designed when graphs where meant to create user interfaces\n     * @method switchLiveMode\n     **/\n    LGraphCanvas.prototype.switchLiveMode = function(transition) {\n        if (!transition) {\n            this.live_mode = !this.live_mode;\n            this.dirty_canvas = true;\n            this.dirty_bgcanvas = true;\n            return;\n        }\n\n        var self = this;\n        var delta = this.live_mode ? 1.1 : 0.9;\n        if (this.live_mode) {\n            this.live_mode = false;\n            this.editor_alpha = 0.1;\n        }\n\n        var t = setInterval(function() {\n            self.editor_alpha *= delta;\n            self.dirty_canvas = true;\n            self.dirty_bgcanvas = true;\n\n            if (delta < 1 && self.editor_alpha < 0.01) {\n                clearInterval(t);\n                if (delta < 1) {\n                    self.live_mode = true;\n                }\n            }\n            if (delta > 1 && self.editor_alpha > 0.99) {\n                clearInterval(t);\n                self.editor_alpha = 1;\n            }\n        }, 1);\n    };\n\n    LGraphCanvas.prototype.onNodeSelectionChange = function(node) {\n        return; //disabled\n    };\n\n    /* this is an implementation for touch not in production and not ready\n     */\n    /*LGraphCanvas.prototype.touchHandler = function(event) {\n        //alert(\"foo\");\n        var touches = event.changedTouches,\n            first = touches[0],\n            type = \"\";\n\n        switch (event.type) {\n            case \"touchstart\":\n                type = \"mousedown\";\n                break;\n            case \"touchmove\":\n                type = \"mousemove\";\n                break;\n            case \"touchend\":\n                type = \"mouseup\";\n                break;\n            default:\n                return;\n        }\n\n        //initMouseEvent(type, canBubble, cancelable, view, clickCount,\n        //           screenX, screenY, clientX, clientY, ctrlKey,\n        //           altKey, shiftKey, metaKey, button, relatedTarget);\n\n        // this is eventually a Dom object, get the LGraphCanvas back\n        if(typeof this.getCanvasWindow == \"undefined\"){\n            var window = this.lgraphcanvas.getCanvasWindow();\n        }else{\n            var window = this.getCanvasWindow();\n        }\n        \n        var document = window.document;\n\n        var simulatedEvent = document.createEvent(\"MouseEvent\");\n        simulatedEvent.initMouseEvent(\n            type,\n            true,\n            true,\n            window,\n            1,\n            first.screenX,\n            first.screenY,\n            first.clientX,\n            first.clientY,\n            false,\n            false,\n            false,\n            false,\n            0, //left\n            null\n        );\n        first.target.dispatchEvent(simulatedEvent);\n        event.preventDefault();\n    };*/\n\n    /* CONTEXT MENU ********************/\n\n    LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) {\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var group = new LiteGraph.LGraphGroup();\n        group.pos = canvas.convertEventToCanvasOffset(mouse_event);\n        canvas.graph.add(group);\n    };\n\n    /**\n     * Determines the furthest nodes in each direction\n     * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted\n     * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}\n     */\n    LGraphCanvas.getBoundaryNodes = function(nodes) {\n        let top = null;\n        let right = null;\n        let bottom = null;\n        let left = null;\n        for (const nID in nodes) {\n            const node = nodes[nID];\n            const [x, y] = node.pos;\n            const [width, height] = node.size;\n\n            if (top === null || y < top.pos[1]) {\n                top = node;\n            }\n            if (right === null || x + width > right.pos[0] + right.size[0]) {\n                right = node;\n            }\n            if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) {\n                bottom = node;\n            }\n            if (left === null || x < left.pos[0]) {\n                left = node;\n            }\n        }\n\n        return {\n            \"top\": top,\n            \"right\": right,\n            \"bottom\": bottom,\n            \"left\": left\n        };\n    }\n    /**\n     * Determines the furthest nodes in each direction for the currently selected nodes\n     * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}\n     */\n    LGraphCanvas.prototype.boundaryNodesForSelection = function() {\n        return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes));\n    }\n\n    /**\n     *\n     * @param {LGraphNode[]} nodes a list of nodes\n     * @param {\"top\"|\"bottom\"|\"left\"|\"right\"} direction Direction to align the nodes\n     * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction)\n     */\n    LGraphCanvas.alignNodes = function (nodes, direction, align_to) {\n        if (!nodes) {\n            return;\n        }\n\n        const canvas = LGraphCanvas.active_canvas;\n        let boundaryNodes = []\n        if (align_to === undefined) {\n            boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes)\n        } else {\n            boundaryNodes = {\n                \"top\": align_to,\n                \"right\": align_to,\n                \"bottom\": align_to,\n                \"left\": align_to\n            }\n        }\n\n        for (const [_, node] of Object.entries(canvas.selected_nodes)) {\n            switch (direction) {\n                case \"right\":\n                    node.pos[0] = boundaryNodes[\"right\"].pos[0] + boundaryNodes[\"right\"].size[0] - node.size[0];\n                    break;\n                case \"left\":\n                    node.pos[0] = boundaryNodes[\"left\"].pos[0];\n                    break;\n                case \"top\":\n                    node.pos[1] = boundaryNodes[\"top\"].pos[1];\n                    break;\n                case \"bottom\":\n                    node.pos[1] = boundaryNodes[\"bottom\"].pos[1] + boundaryNodes[\"bottom\"].size[1] - node.size[1];\n                    break;\n            }\n        }\n\n        canvas.dirty_canvas = true;\n        canvas.dirty_bgcanvas = true;\n    };\n\n    LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) {\n        new LiteGraph.ContextMenu([\"Top\", \"Bottom\", \"Left\", \"Right\"], {\n            event: event,\n            callback: inner_clicked,\n            parentMenu: prev_menu,\n        });\n\n        function inner_clicked(value) {\n            LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node);\n        }\n    }\n\n    LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) {\n        new LiteGraph.ContextMenu([\"Top\", \"Bottom\", \"Left\", \"Right\"], {\n            event: event,\n            callback: inner_clicked,\n            parentMenu: prev_menu,\n        });\n\n        function inner_clicked(value) {\n            LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase());\n        }\n    }\n\n    LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) {\n\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n        var graph = canvas.graph;\n        if (!graph)\n            return;\n\n        function inner_onMenuAdded(base_category ,prev_menu){\n    \n            var categories  = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function(category){return category.startsWith(base_category)});\n            var entries = [];\n    \n            categories.map(function(category){\n    \n                if (!category) \n                    return;\n    \n                var base_category_regex = new RegExp('^(' + base_category + ')');\n                var category_name = category.replace(base_category_regex,\"\").split('/')[0];\n                var category_path = base_category  === '' ? category_name + '/' : base_category + category_name + '/';\n    \n                var name = category_name;\n                if(name.indexOf(\"::\") != -1) //in case it has a namespace like \"shader::math/rand\" it hides the namespace\n                    name = name.split(\"::\")[1];\n                        \n                var index = entries.findIndex(function(entry){return entry.value === category_path});\n                if (index === -1) {\n                    entries.push({ value: category_path, content: name, has_submenu: true, callback : function(value, event, mouseEvent, contextMenu){\n                        inner_onMenuAdded(value.value, contextMenu)\n                    }});\n                }\n                \n            });\n    \n            var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter );\n            nodes.map(function(node){\n    \n                if (node.skip_list)\n                    return;\n    \n                var entry = { value: node.type, content: node.title, has_submenu: false , callback : function(value, event, mouseEvent, contextMenu){\n                    \n                        var first_event = contextMenu.getFirstEvent();\n                        canvas.graph.beforeChange();\n                        var node = LiteGraph.createNode(value.value);\n                        if (node) {\n                            node.pos = canvas.convertEventToCanvasOffset(first_event);\n                            canvas.graph.add(node);\n                        }\n                        if(callback)\n                            callback(node);\n                        canvas.graph.afterChange();\n                    \n                    }\n                }\n    \n                entries.push(entry);\n    \n            });\n    \n            new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window );\n    \n        }\n    \n        inner_onMenuAdded('',prev_menu);\n        return false;\n    \n    };\n\n    LGraphCanvas.onMenuCollapseAll = function() {};\n\n    LGraphCanvas.onMenuNodeEdit = function() {};\n\n    LGraphCanvas.showMenuNodeOptionalInputs = function(\n        v,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var options = node.optional_inputs;\n        if (node.onGetInputs) {\n            options = node.onGetInputs();\n        }\n\n        var entries = [];\n        if (options) {\n            for (var i=0; i < options.length; i++) {\n                var entry = options[i];\n                if (!entry) {\n                    entries.push(null);\n                    continue;\n                }\n                var label = entry[0];\n\t\t\t\tif(!entry[2])\n\t\t\t\t\tentry[2] = {};\n\n                if (entry[2].label) {\n                    label = entry[2].label;\n                }\n\n\t\t\t\tentry[2].removable = true;\n                var data = { content: label, value: entry };\n                if (entry[1] == LiteGraph.ACTION) {\n                    data.className = \"event\";\n                }\n                entries.push(data);\n            }\n        }\n\n        if (node.onMenuNodeInputs) {\n            var retEntries = node.onMenuNodeInputs(entries);\n            if(retEntries) entries = retEntries;\n        }\n\n        if (!entries.length) {\n\t\t\tconsole.log(\"no input entries\");\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, e, prev) {\n            if (!node) {\n                return;\n            }\n\n            if (v.callback) {\n                v.callback.call(that, node, v, e, prev);\n            }\n\n            if (v.value) {\n\t\t\t\tnode.graph.beforeChange();\n                node.addInput(v.value[0], v.value[1], v.value[2]);\n\n                if (node.onNodeInputAdd) { // callback to the node when adding a slot\n                    node.onNodeInputAdd(v.value);\n                }\n                node.setDirtyCanvas(true, true);\n\t\t\t\tnode.graph.afterChange();\n            }\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.showMenuNodeOptionalOutputs = function(\n        v,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var options = node.optional_outputs;\n        if (node.onGetOutputs) {\n            options = node.onGetOutputs();\n        }\n\n        var entries = [];\n        if (options) {\n            for (var i=0; i < options.length; i++) {\n                var entry = options[i];\n                if (!entry) {\n                    //separator?\n                    entries.push(null);\n                    continue;\n                }\n\n                if (\n                    node.flags &&\n                    node.flags.skip_repeated_outputs &&\n                    node.findOutputSlot(entry[0]) != -1\n                ) {\n                    continue;\n                } //skip the ones already on\n                var label = entry[0];\n\t\t\t\tif(!entry[2])\n\t\t\t\t\tentry[2] = {};\n                if (entry[2].label) {\n                    label = entry[2].label;\n                }\n\t\t\t\tentry[2].removable = true;\n                var data = { content: label, value: entry };\n                if (entry[1] == LiteGraph.EVENT) {\n                    data.className = \"event\";\n                }\n                entries.push(data);\n            }\n        }\n\n        if (this.onMenuNodeOutputs) {\n            entries = this.onMenuNodeOutputs(entries);\n        }\n        if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted\n            if (node.findOutputSlot(\"onExecuted\") == -1){\n                entries.push({content: \"On Executed\", value: [\"onExecuted\", LiteGraph.EVENT, {nameLocked: true}], className: \"event\"}); //, opts: {}\n            }\n        }\n        // add callback for modifing the menu elements onMenuNodeOutputs\n        if (node.onMenuNodeOutputs) {\n            var retEntries = node.onMenuNodeOutputs(entries);\n            if(retEntries) entries = retEntries;\n        }\n\n        if (!entries.length) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, e, prev) {\n            if (!node) {\n                return;\n            }\n\n            if (v.callback) {\n                v.callback.call(that, node, v, e, prev);\n            }\n\n            if (!v.value) {\n                return;\n            }\n\n            var value = v.value[1];\n\n            if (\n                value &&\n                (value.constructor === Object || value.constructor === Array)\n            ) {\n                //submenu why?\n                var entries = [];\n                for (var i in value) {\n                    entries.push({ content: i, value: value[i] });\n                }\n                new LiteGraph.ContextMenu(entries, {\n                    event: e,\n                    callback: inner_clicked,\n                    parentMenu: prev_menu,\n                    node: node\n                });\n                return false;\n            } else {\n\t\t\t\tnode.graph.beforeChange();\n                node.addOutput(v.value[0], v.value[1], v.value[2]);\n\n                if (node.onNodeOutputAdd) { // a callback to the node when adding a slot\n                    node.onNodeOutputAdd(v.value);\n                }\n                node.setDirtyCanvas(true, true);\n\t\t\t\tnode.graph.afterChange();\n            }\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onShowMenuNodeProperties = function(\n        value,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node || !node.properties) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var entries = [];\n        for (var i in node.properties) {\n            var value = node.properties[i] !== undefined ? node.properties[i] : \" \";\n\t\t\tif( typeof value == \"object\" )\n\t\t\t\tvalue = JSON.stringify(value);\n\t\t\tvar info = node.getPropertyInfo(i);\n\t\t\tif(info.type == \"enum\" || info.type == \"combo\")\n\t\t\t\tvalue = LGraphCanvas.getPropertyPrintableValue( value, info.values );\n\n            //value could contain invalid html characters, clean that\n            value = LGraphCanvas.decodeHTML(value);\n            entries.push({\n                content:\n                    \"<span class='property_name'>\" +\n                    (info.label ? info.label : i) +\n                    \"</span>\" +\n                    \"<span class='property_value'>\" +\n                    value +\n                    \"</span>\",\n                value: i\n            });\n        }\n        if (!entries.length) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                allow_html: true,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, options, e, prev) {\n            if (!node) {\n                return;\n            }\n            var rect = this.getBoundingClientRect();\n            canvas.showEditPropertyValue(node, v.value, {\n                position: [rect.left, rect.top]\n            });\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.decodeHTML = function(str) {\n        var e = document.createElement(\"div\");\n        e.innerText = str;\n        return e.innerHTML;\n    };\n\n    LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) {\n        if (!node) {\n            return;\n        }\n        \n\t\tvar fApplyMultiNode = function(node){\n\t\t\tnode.size = node.computeSize();\n\t\t\tif (node.onResize)\n\t\t\t\tnode.onResize(node.size);\n\t\t}\n\t\t\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.prototype.showLinkMenu = function(link, e) {\n        var that = this;\n\t\t// console.log(link);\n\t\tvar node_left = that.graph.getNodeById( link.origin_id );\n\t\tvar node_right = that.graph.getNodeById( link.target_id );\n\t\tvar fromType = false;\n\t\tif (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type;\n        var destType = false;\n\t\tif (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type;\n\t\t\n\t\tvar options = [\"Add Node\",null,\"Delete\",null];\n\t\t\n\t\t\n        var menu = new LiteGraph.ContextMenu(options, {\n            event: e,\n\t\t\ttitle: link.data != null ? link.data.constructor.name : null,\n            callback: inner_clicked\n        });\n\n        function inner_clicked(v,options,e) {\n            switch (v) {\n                case \"Add Node\":\n\t\t\t\t\tLGraphCanvas.onMenuAdd(null, null, e, menu, function(node){\n\t\t\t\t\t\t// console.debug(\"node autoconnect\");\n\t\t\t\t\t\tif(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length){\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// leave the connection type checking inside connectByType\n\t\t\t\t\t\tif (node_left.connectByType( link.origin_slot, node, fromType )){\n                        \tnode.connectByType( link.target_slot, node_right, destType );\n                            node.pos[0] -= node.size[0] * 0.5;\n                        }\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t\t\n                case \"Delete\":\n                    that.graph.removeLink(link.id);\n                    break;\n                default:\n\t\t\t\t\t/*var nodeCreated = createDefaultNodeForSlot({   nodeFrom: node_left\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotFrom: link.origin_slot\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeTo: node\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotTo: link.target_slot\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,e: e\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: \"AUTO\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\tif(nodeCreated) console.log(\"new node in beetween \"+v+\" created\");*/\n            }\n        }\n\n        return false;\n    };\n    \n \tLGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection\n        var optPass = optPass || {};\n        var opts = Object.assign({   nodeFrom: null // input\n                                    ,slotFrom: null // input\n                                    ,nodeTo: null   // output\n                                    ,slotTo: null   // output\n                                    ,position: []\t// pass the event coords\n\t\t\t\t\t\t\t\t  \t,nodeType: null\t// choose a nodetype to add, AUTO to set at first good\n\t\t\t\t\t\t\t\t  \t,posAdd:[0,0]\t// adjust x,y\n\t\t\t\t\t\t\t\t  \t,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h\n                                }\n                                ,optPass\n                            );\n        var that = this;\n        \n        var isFrom = opts.nodeFrom && opts.slotFrom!==null;\n        var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null;\n\t\n        if (!isFrom && !isTo){\n            console.warn(\"No data passed to createDefaultNodeForSlot \"+opts.nodeFrom+\" \"+opts.slotFrom+\" \"+opts.nodeTo+\" \"+opts.slotTo);\n            return false;\n        }\n\t\tif (!opts.nodeType){\n            console.warn(\"No type to createDefaultNodeForSlot\");\n            return false;\n        }\n        \n        var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;\n        var slotX = isFrom ? opts.slotFrom : opts.slotTo;\n        \n        var iSlotConn = false;\n        switch (typeof slotX){\n            case \"string\":\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            case \"object\":\n                // ok slotX\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);\n            break;\n            case \"number\":\n                iSlotConn = slotX;\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n\t\t\tcase \"undefined\":\n            default:\n                // bad ?\n                //iSlotConn = 0;\n                console.warn(\"Cant get slot information \"+slotX);\n                return false;\n        }\n\t\n\t\tif (slotX===false || iSlotConn===false){\n\t\t\tconsole.warn(\"createDefaultNodeForSlot bad slotX \"+slotX+\" \"+iSlotConn);\n\t\t}\n\t\t\n\t\t// check for defaults nodes for this slottype\n\t\tvar fromSlotType = slotX.type==LiteGraph.EVENT?\"_event_\":slotX.type;\n\t\tvar slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;\n\t\tif(slotTypesDefault && slotTypesDefault[fromSlotType]){\n\t\t\tif (slotX.link !== null) {\n\t\t\t\t// is connected\n\t\t\t}else{\n\t\t\t\t// is not not connected\n\t\t\t}\n\t\t\tnodeNewType = false;\n\t\t\tif(typeof slotTypesDefault[fromSlotType] == \"object\" || typeof slotTypesDefault[fromSlotType] == \"array\"){\n\t\t\t\tfor(var typeX in slotTypesDefault[fromSlotType]){\n\t\t\t\t\tif (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == \"AUTO\"){\n\t\t\t\t\t\tnodeNewType = slotTypesDefault[fromSlotType][typeX];\n\t\t\t\t\t\t// console.log(\"opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: \"+opts.nodeType);\n\t\t\t\t\t\tbreak; // --------\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\tif (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == \"AUTO\") nodeNewType = slotTypesDefault[fromSlotType];\n\t\t\t}\n\t\t\tif (nodeNewType) {\n\t\t\t\tvar nodeNewOpts = false;\n\t\t\t\tif (typeof nodeNewType == \"object\" && nodeNewType.node){\n\t\t\t\t\tnodeNewOpts = nodeNewType;\n\t\t\t\t\tnodeNewType = nodeNewType.node;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t//that.graph.beforeChange();\n\t\t\t\t\n\t\t\t\tvar newNode = LiteGraph.createNode(nodeNewType);\n\t\t\t\tif(newNode){\n\t\t\t\t\t// if is object pass options\n\t\t\t\t\tif (nodeNewOpts){\n\t\t\t\t\t\tif (nodeNewOpts.properties) {\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.properties) {\n\t\t\t\t\t\t\t\tnewNode.addProperty( i, nodeNewOpts.properties[i] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.inputs) {\n\t\t\t\t\t\t\tnewNode.inputs = [];\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.inputs) {\n\t\t\t\t\t\t\t\tnewNode.addOutput(\n\t\t\t\t\t\t\t\t\tnodeNewOpts.inputs[i][0],\n\t\t\t\t\t\t\t\t\tnodeNewOpts.inputs[i][1]\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.outputs) {\n\t\t\t\t\t\t\tnewNode.outputs = [];\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.outputs) {\n\t\t\t\t\t\t\t\tnewNode.addOutput(\n\t\t\t\t\t\t\t\t\tnodeNewOpts.outputs[i][0],\n\t\t\t\t\t\t\t\t\tnodeNewOpts.outputs[i][1]\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.title) {\n\t\t\t\t\t\t\tnewNode.title = nodeNewOpts.title;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.json) {\n\t\t\t\t\t\t\tnewNode.configure(nodeNewOpts.json);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// add the node\n\t\t\t\t\tthat.graph.add(newNode);\n\t\t\t\t\tnewNode.pos = [\topts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0)\n\t\t\t\t\t\t\t\t   \t,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/\n\t\t\t\t\t\n\t\t\t\t\t//that.graph.afterChange();\n\t\t\t\t\t\n\t\t\t\t\t// connect the two!\n\t\t\t\t\tif (isFrom){\n\t\t\t\t\t\topts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType );\n\t\t\t\t\t}else{\n\t\t\t\t\t\topts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType );\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// if connecting in between\n\t\t\t\t\tif (isFrom && isTo){\n\t\t\t\t\t\t// TODO\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treturn true;\n\t\t\t\t\t\n\t\t\t\t}else{\n\t\t\t\t\tconsole.log(\"failed creating \"+nodeNewType);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n \n    LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection\n        var optPass = optPass || {};\n        var opts = Object.assign({   nodeFrom: null  // input\n                                    ,slotFrom: null // input\n                                    ,nodeTo: null   // output\n                                    ,slotTo: null   // output\n                                    ,e: null\n                                }\n                                ,optPass\n                            );\n        var that = this;\n        \n        var isFrom = opts.nodeFrom && opts.slotFrom;\n        var isTo = !isFrom && opts.nodeTo && opts.slotTo;\n        \n        if (!isFrom && !isTo){\n            console.warn(\"No data passed to showConnectionMenu\");\n            return false;\n        }\n        \n        var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;\n        var slotX = isFrom ? opts.slotFrom : opts.slotTo;\n        \n        var iSlotConn = false;\n        switch (typeof slotX){\n            case \"string\":\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            case \"object\":\n                // ok slotX\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);\n            break;\n            case \"number\":\n                iSlotConn = slotX;\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            default:\n                // bad ?\n                //iSlotConn = 0;\n                console.warn(\"Cant get slot information \"+slotX);\n                return false;\n        }\n            \n\t\tvar options = [\"Add Node\",null];\n\t\t\n\t\tif (that.allow_searchbox){\n\t\t\toptions.push(\"Search\");\n\t\t\toptions.push(null);\n\t\t}\n\t\t\n\t\t// get defaults nodes for this slottype\n\t\tvar fromSlotType = slotX.type==LiteGraph.EVENT?\"_event_\":slotX.type;\n\t\tvar slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;\n\t\tif(slotTypesDefault && slotTypesDefault[fromSlotType]){\n\t\t\tif(typeof slotTypesDefault[fromSlotType] == \"object\" || typeof slotTypesDefault[fromSlotType] == \"array\"){\n\t\t\t\tfor(var typeX in slotTypesDefault[fromSlotType]){\n\t\t\t\t\toptions.push(slotTypesDefault[fromSlotType][typeX]);\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\toptions.push(slotTypesDefault[fromSlotType]);\n\t\t\t}\n\t\t}\n\t\t\n\t\t// build menu\n        var menu = new LiteGraph.ContextMenu(options, {\n            event: opts.e,\n\t\t\ttitle: (slotX && slotX.name!=\"\" ? (slotX.name + (fromSlotType?\" | \":\"\")) : \"\")+(slotX && fromSlotType ? fromSlotType : \"\"),\n            callback: inner_clicked\n        });\n        \n\t\t// callback\n        function inner_clicked(v,options,e) {\n            //console.log(\"Process showConnectionMenu selection\");\n            switch (v) {\n                case \"Add Node\":\n                    LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){\n                        if (isFrom){\n                            opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType );\n                        }else{\n                            opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType );\n                        }\n                    });\n                    break;\n\t\t\t\tcase \"Search\":\n\t\t\t\t\tif(isFrom){\n\t\t\t\t\t\tthat.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType});\n\t\t\t\t\t}else{\n\t\t\t\t\t\tthat.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType});\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n                default:\n\t\t\t\t\t// check for defaults nodes for this slottype\n\t\t\t\t\tvar nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: v\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\tif (nodeCreated){\n\t\t\t\t\t\t// new node created\n\t\t\t\t\t\t//console.log(\"node \"+v+\" created\")\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// failed or v is not in defaults\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n            }\n        }   \n        \n        return false;\n    };\n\n    // TODO refactor :: this is used fot title but not for properties!\n    LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) {\n        var input_html = \"\";\n        var property = item.property || \"title\";\n        var value = node[property];\n\n        // TODO refactor :: use createDialog ?\n        \n        var dialog = document.createElement(\"div\");\n        dialog.is_modified = false;\n        dialog.className = \"graphdialog\";\n        dialog.innerHTML =\n            \"<span class='name'></span><input autofocus type='text' class='value'/><button>OK</button>\";\n        dialog.close = function() {\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n        var title = dialog.querySelector(\".name\");\n        title.innerText = property;\n        var input = dialog.querySelector(\".value\");\n        if (input) {\n            input.value = value;\n            input.addEventListener(\"blur\", function(e) {\n                this.focus();\n            });\n            input.addEventListener(\"keydown\", function(e) {\n                dialog.is_modified = true;\n                if (e.keyCode == 27) {\n                    //ESC\n                    dialog.close();\n                } else if (e.keyCode == 13) {\n                    inner(); // save\n                } else if (e.keyCode != 13 && e.target.localName != \"textarea\") {\n                    return;\n                }\n                e.preventDefault();\n                e.stopPropagation();\n            });\n        }\n\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n\n        var rect = canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", inner);\n        canvas.parentNode.appendChild(dialog);\n\n        if(input) input.focus();\n        \n        var dialogCloseTimer = null;\n        dialog.addEventListener(\"mouseleave\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        dialog.addEventListener(\"mouseenter\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        \n        function inner() {\n            if(input) setValue(input.value);\n        }\n\n        function setValue(value) {\n            if (item.type == \"Number\") {\n                value = Number(value);\n            } else if (item.type == \"Boolean\") {\n                value = Boolean(value);\n            }\n            node[property] = value;\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n            node.setDirtyCanvas(true, true);\n        }\n    };\n\n    // refactor: there are different dialogs, some uses createDialog some dont\n    LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) {\n        var that = this;\n        var input_html = \"\";\n        title = title || \"\";\n\n        var dialog = document.createElement(\"div\");\n        dialog.is_modified = false;\n        dialog.className = \"graphdialog rounded\";\n        if(multiline)\n\t        dialog.innerHTML = \"<span class='name'></span> <textarea autofocus class='value'></textarea><button class='rounded'>OK</button>\";\n\t\telse\n        \tdialog.innerHTML = \"<span class='name'></span> <input autofocus type='text' class='value'/><button class='rounded'>OK</button>\";\n        dialog.close = function() {\n            that.prompt_box = null;\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n        canvas.parentNode.appendChild(dialog);\n        \n        if (this.ds.scale > 1) {\n            dialog.style.transform = \"scale(\" + this.ds.scale + \")\";\n        }\n\n        var dialogCloseTimer = null;\n        var prevent_timeout = false;\n        LiteGraph.pointerListenerAdd(dialog,\"leave\", function(e) {\n            if (prevent_timeout)\n                return;\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        LiteGraph.pointerListenerAdd(dialog,\"enter\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        var selInDia = dialog.querySelectorAll(\"select\");\n        if (selInDia){\n            // if filtering, check focus changed to comboboxes and prevent closing\n            selInDia.forEach(function(selIn) {\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            });\n        }\n\n        if (that.prompt_box) {\n            that.prompt_box.close();\n        }\n        that.prompt_box = dialog;\n\n        var first = null;\n        var timeout = null;\n        var selected = null;\n\n        var name_element = dialog.querySelector(\".name\");\n        name_element.innerText = title;\n        var value_element = dialog.querySelector(\".value\");\n        value_element.value = value;\n\n        var input = value_element;\n        input.addEventListener(\"keydown\", function(e) {\n            dialog.is_modified = true;\n            if (e.keyCode == 27) {\n                //ESC\n                dialog.close();\n            } else if (e.keyCode == 13 && e.target.localName != \"textarea\") {\n                if (callback) {\n                    callback(this.value);\n                }\n                dialog.close();\n            } else {\n                return;\n            }\n            e.preventDefault();\n            e.stopPropagation();\n        });\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", function(e) {\n            if (callback) {\n                callback(input.value);\n            }\n            that.setDirty(true);\n            dialog.close();\n        });\n\n        var rect = canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n\n        setTimeout(function() {\n            input.focus();\n        }, 10);\n\n        return dialog;\n    };\n\n    LGraphCanvas.search_limit = -1;\n    LGraphCanvas.prototype.showSearchBox = function(event, options) {\n        // proposed defaults\n        var def_options = { slot_from: null\n                        ,node_from: null\n                        ,node_to: null\n                        ,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out\n                        ,type_filter_in: false                          // these are default: pass to set initially set values\n                        ,type_filter_out: false\n                        ,show_general_if_none_on_typefilter: true\n                        ,show_general_after_typefiltered: true\n                        ,hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave\n                        ,show_all_if_empty: true\n                        ,show_all_on_open: LiteGraph.search_show_all_on_open\n                    };\n        options = Object.assign(def_options, options || {});\n        \n\t\t//console.log(options);\n\t\t\n        var that = this;\n        var input_html = \"\";\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n        var root_document = canvas.ownerDocument || document;\n\n        var dialog = document.createElement(\"div\");\n        dialog.className = \"litegraph litesearchbox graphdialog rounded\";\n        dialog.innerHTML = \"<span class='name'>Search</span> <input autofocus type='text' class='value rounded'/>\";\n        if (options.do_type_filter){\n            dialog.innerHTML += \"<select class='slot_in_type_filter'><option value=''></option></select>\";\n            dialog.innerHTML += \"<select class='slot_out_type_filter'><option value=''></option></select>\";\n        }\n        dialog.innerHTML += \"<div class='helper'></div>\";\n        \n        if( root_document.fullscreenElement )\n\t        root_document.fullscreenElement.appendChild(dialog);\n\t\telse\n\t\t{\n\t\t    root_document.body.appendChild(dialog);\n\t\t\troot_document.body.style.overflow = \"hidden\";\n\t\t}\n        // dialog element has been appended\n        \n        if (options.do_type_filter){\n            var selIn = dialog.querySelector(\".slot_in_type_filter\");\n            var selOut = dialog.querySelector(\".slot_out_type_filter\");\n        }\n        \n        dialog.close = function() {\n            that.search_box = null;\n\t\t\tthis.blur();\n            canvas.focus();\n\t\t\troot_document.body.style.overflow = \"\";\n\n            setTimeout(function() {\n                that.canvas.focus();\n            }, 20); //important, if canvas loses focus keys wont be captured\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n\n        if (this.ds.scale > 1) {\n            dialog.style.transform = \"scale(\" + this.ds.scale + \")\";\n        }\n\n        // hide on mouse leave\n        if(options.hide_on_mouse_leave){\n            var prevent_timeout = false;\n            var timeout_close = null;\n            LiteGraph.pointerListenerAdd(dialog,\"enter\", function(e) {\n                if (timeout_close) {\n                    clearTimeout(timeout_close);\n                    timeout_close = null;\n                }\n            });\n            LiteGraph.pointerListenerAdd(dialog,\"leave\", function(e) {\n                if (prevent_timeout){\n                    return;\n                }\n                timeout_close = setTimeout(function() {\n                    dialog.close();\n                }, 500);\n            });\n            // if filtering, check focus changed to comboboxes and prevent closing\n            if (options.do_type_filter){\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n                selOut.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selOut.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selOut.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            }\n        }\n\n        if (that.search_box) {\n            that.search_box.close();\n        }\n        that.search_box = dialog;\n\n        var helper = dialog.querySelector(\".helper\");\n\n        var first = null;\n        var timeout = null;\n        var selected = null;\n\n        var input = dialog.querySelector(\"input\");\n        if (input) {\n            input.addEventListener(\"blur\", function(e) {\n                if(that.search_box)\n                    this.focus();\n            });\n            input.addEventListener(\"keydown\", function(e) {\n                if (e.keyCode == 38) {\n                    //UP\n                    changeSelection(false);\n                } else if (e.keyCode == 40) {\n                    //DOWN\n                    changeSelection(true);\n                } else if (e.keyCode == 27) {\n                    //ESC\n                    dialog.close();\n                } else if (e.keyCode == 13) {\n                    refreshHelper();\n                    if (selected) {\n                        select(selected.innerHTML);\n                    } else if (first) {\n                        select(first);\n                    } else {\n                        dialog.close();\n                    }\n                } else {\n                    if (timeout) {\n                        clearInterval(timeout);\n                    }\n                    timeout = setTimeout(refreshHelper, 250);\n                    return;\n                }\n                e.preventDefault();\n                e.stopPropagation();\n\t\t\t\te.stopImmediatePropagation();\n\t\t\t\treturn true;\n            });\n        }\n        \n        // if should filter on type, load and fill selected and choose elements if passed\n        if (options.do_type_filter){\n            if (selIn){\n                var aSlots = LiteGraph.slot_types_in;\n                var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length;\n                \n                if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION)\n                    options.type_filter_in = \"_event_\";\n                /* this will filter on * .. but better do it manually in case\n                else if(options.type_filter_in === \"\" || options.type_filter_in === 0)\n                    options.type_filter_in = \"*\";*/\n                \n                for (var iK=0; iK<nSlots; iK++){\n                    var opt = document.createElement('option');\n                    opt.value = aSlots[iK];\n                    opt.innerHTML = aSlots[iK];\n                    selIn.appendChild(opt);\n                    if(options.type_filter_in !==false && (options.type_filter_in+\"\").toLowerCase() == (aSlots[iK]+\"\").toLowerCase()){\n                        //selIn.selectedIndex ..\n                        opt.selected = true;\n\t\t\t\t\t\t//console.log(\"comparing IN \"+options.type_filter_in+\" :: \"+aSlots[iK]);\n\t                }else{\n\t\t\t\t\t\t//console.log(\"comparing OUT \"+options.type_filter_in+\" :: \"+aSlots[iK]);\n\t\t\t\t\t}\n\t\t\t\t}\n                selIn.addEventListener(\"change\",function(){\n                    refreshHelper();\n                });\n            }\n            if (selOut){\n                var aSlots = LiteGraph.slot_types_out;\n                var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; \n                \n                if (options.type_filter_out == LiteGraph.EVENT || options.type_filter_out == LiteGraph.ACTION)\n                    options.type_filter_out = \"_event_\";\n                /* this will filter on * .. but better do it manually in case\n                else if(options.type_filter_out === \"\" || options.type_filter_out === 0)\n                    options.type_filter_out = \"*\";*/\n                \n                for (var iK=0; iK<nSlots; iK++){\n                    var opt = document.createElement('option');\n                    opt.value = aSlots[iK];\n                    opt.innerHTML = aSlots[iK];\n                    selOut.appendChild(opt);\n                    if(options.type_filter_out !==false && (options.type_filter_out+\"\").toLowerCase() == (aSlots[iK]+\"\").toLowerCase()){\n                        //selOut.selectedIndex ..\n                        opt.selected = true;\n                    }\n                }\n                selOut.addEventListener(\"change\",function(){\n                    refreshHelper();\n                });\n            }\n        }\n        \n        //compute best position\n        var rect = canvas.getBoundingClientRect();\n\n        var left = ( event ? event.clientX : (rect.left + rect.width * 0.5) ) - 80;\n        var top = ( event ? event.clientY : (rect.top + rect.height * 0.5) ) - 20;\n        dialog.style.left = left + \"px\";\n        dialog.style.top = top + \"px\";\n\n\t\t//To avoid out of screen problems\n\t\tif(event.layerY > (rect.height - 200)) \n            helper.style.maxHeight = (rect.height - event.layerY - 20) + \"px\";\n\n\t\t/*\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n        canvas.parentNode.appendChild(dialog);\n\t\t*/\n\n        input.focus();\n        if (options.show_all_on_open) refreshHelper();\n\n        function select(name) {\n            if (name) {\n                if (that.onSearchBoxSelection) {\n                    that.onSearchBoxSelection(name, event, graphcanvas);\n                } else {\n                    var extra = LiteGraph.searchbox_extras[name.toLowerCase()];\n                    if (extra) {\n                        name = extra.type;\n                    }\n\n\t\t\t\t\tgraphcanvas.graph.beforeChange();\n                    var node = LiteGraph.createNode(name);\n                    if (node) {\n                        node.pos = graphcanvas.convertEventToCanvasOffset(\n                            event\n                        );\n                        graphcanvas.graph.add(node, false);\n                    }\n\n                    if (extra && extra.data) {\n                        if (extra.data.properties) {\n                            for (var i in extra.data.properties) {\n                                node.addProperty( i, extra.data.properties[i] );\n                            }\n                        }\n                        if (extra.data.inputs) {\n                            node.inputs = [];\n                            for (var i in extra.data.inputs) {\n                                node.addOutput(\n                                    extra.data.inputs[i][0],\n                                    extra.data.inputs[i][1]\n                                );\n                            }\n                        }\n                        if (extra.data.outputs) {\n                            node.outputs = [];\n                            for (var i in extra.data.outputs) {\n                                node.addOutput(\n                                    extra.data.outputs[i][0],\n                                    extra.data.outputs[i][1]\n                                );\n                            }\n                        }\n                        if (extra.data.title) {\n                            node.title = extra.data.title;\n                        }\n                        if (extra.data.json) {\n                            node.configure(extra.data.json);\n                        }\n\n                    }\n\n                    // join node after inserting\n                    if (options.node_from){\n                        var iS = false;\n                        switch (typeof options.slot_from){\n                            case \"string\":\n                                iS = options.node_from.findOutputSlot(options.slot_from);    \n                            break;\n                            case \"object\":\n                                if (options.slot_from.name){\n                                    iS = options.node_from.findOutputSlot(options.slot_from.name);\n                                }else{\n                                    iS = -1;\n                                }\n                                if (iS==-1 && typeof options.slot_from.slot_index !== \"undefined\") iS = options.slot_from.slot_index;\n                            break;\n                            case \"number\":\n                                iS = options.slot_from;\n                            break;\n                            default:\n                                iS = 0; // try with first if no name set\n                        }\n                        if (typeof options.node_from.outputs[iS] !== \"undefined\"){\n                            if (iS!==false && iS>-1){\n                                options.node_from.connectByType( iS, node, options.node_from.outputs[iS].type );\n                            }\n                        }else{\n                            // console.warn(\"cant find slot \" + options.slot_from);\n                        }\n                    }\n                    if (options.node_to){\n                        var iS = false;\n                        switch (typeof options.slot_from){\n                            case \"string\":\n                                iS = options.node_to.findInputSlot(options.slot_from);    \n                            break;\n                            case \"object\":\n                                if (options.slot_from.name){\n                                    iS = options.node_to.findInputSlot(options.slot_from.name);\n                                }else{\n                                    iS = -1;\n                                }\n                                if (iS==-1 && typeof options.slot_from.slot_index !== \"undefined\") iS = options.slot_from.slot_index;\n                            break;\n                            case \"number\":\n                                iS = options.slot_from;\n                            break;\n                            default:\n                                iS = 0; // try with first if no name set\n                        }\n                        if (typeof options.node_to.inputs[iS] !== \"undefined\"){\n                            if (iS!==false && iS>-1){\n                                // try connection\n                                options.node_to.connectByTypeOutput(iS,node,options.node_to.inputs[iS].type);\n                            }\n                        }else{\n                            // console.warn(\"cant find slot_nodeTO \" + options.slot_from);\n                        }\n                    }\n                    \n                    graphcanvas.graph.afterChange();\n                }\n            }\n\n            dialog.close();\n        }\n\n        function changeSelection(forward) {\n            var prev = selected;\n            if (selected) {\n                selected.classList.remove(\"selected\");\n            }\n            if (!selected) {\n                selected = forward\n                    ? helper.childNodes[0]\n                    : helper.childNodes[helper.childNodes.length];\n            } else {\n                selected = forward\n                    ? selected.nextSibling\n                    : selected.previousSibling;\n                if (!selected) {\n                    selected = prev;\n                }\n            }\n            if (!selected) {\n                return;\n            }\n            selected.classList.add(\"selected\");\n            selected.scrollIntoView({block: \"end\", behavior: \"smooth\"});\n        }\n\n        function refreshHelper() {\n            timeout = null;\n            var str = input.value;\n            first = null;\n            helper.innerHTML = \"\";\n            if (!str && !options.show_all_if_empty) {\n                return;\n            }\n\n            if (that.onSearchBox) {\n                var list = that.onSearchBox(helper, str, graphcanvas);\n                if (list) {\n                    for (var i = 0; i < list.length; ++i) {\n                        addResult(list[i]);\n                    }\n                }\n            } else {\n                var c = 0;\n                str = str.toLowerCase();\n\t\t\t\tvar filter = graphcanvas.filter || graphcanvas.graph.filter;\n\n                // filter by type preprocess\n                if(options.do_type_filter && that.search_box){\n                    var sIn = that.search_box.querySelector(\".slot_in_type_filter\");\n                    var sOut = that.search_box.querySelector(\".slot_out_type_filter\");\n                }else{\n                    var sIn = false;\n                    var sOut = false;\n                }\n                \n                //extras\n                for (var i in LiteGraph.searchbox_extras) {\n                    var extra = LiteGraph.searchbox_extras[i];\n                    if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) {\n                        continue;\n                    }\n\t\t\t\t\tvar ctor = LiteGraph.registered_node_types[ extra.type ];\n\t\t\t\t\tif( ctor && ctor.filter != filter )\n\t\t\t\t\t\tcontinue;\n                    if( ! inner_test_filter(extra.type) )\n                        continue;\n                    addResult( extra.desc, \"searchbox_extra\" );\n                    if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                        break;\n                    }\n                }\n\n\t\t\t\tvar filtered = null;\n                if (Array.prototype.filter) { //filter supported\n                    var keys = Object.keys( LiteGraph.registered_node_types ); //types\n                    var filtered = keys.filter( inner_test_filter );\n                } else {\n\t\t\t\t\tfiltered = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i) )\n\t\t\t\t\t\t\tfiltered.push(i);\n                    }\n                }\n\n\t\t\t\tfor (var i = 0; i < filtered.length; i++) {\n\t\t\t\t\taddResult(filtered[i]);\n\t\t\t\t\tif ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n                \n                // add general type if filtering\n                if (options.show_general_after_typefiltered\n                    && (sIn.value || sOut.value) \n                ){\n                    filtered_extra = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?\"*\":false, outTypeOverride: sOut&&sOut.value?\"*\":false}) )\n\t\t\t\t\t\t\tfiltered_extra.push(i);\n                    }\n                    for (var i = 0; i < filtered_extra.length; i++) {\n                        addResult(filtered_extra[i], \"generic_type\");\n                        if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                            break;\n                        }\n                    }\n                }\n                \n                // check il filtering gave no results\n                if ((sIn.value || sOut.value) && \n                    ( (helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) )\n                ){\n                    filtered_extra = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i, {skipFilter: true}) )\n\t\t\t\t\t\t\tfiltered_extra.push(i);\n                    }\n                    for (var i = 0; i < filtered_extra.length; i++) {\n                        addResult(filtered_extra[i], \"not_in_filter\");\n                        if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                            break;\n                        }\n                    }\n                }\n                \n\t\t\t\tfunction inner_test_filter( type, optsIn )\n\t\t\t\t{\n                    var optsIn = optsIn || {};\n                    var optsDef = { skipFilter: false\n                                    ,inTypeOverride: false\n                                    ,outTypeOverride: false\n                                  };\n                    var opts = Object.assign(optsDef,optsIn);\n\t\t\t\t\tvar ctor = LiteGraph.registered_node_types[ type ];\n\t\t\t\t\tif(filter && ctor.filter != filter )\n\t\t\t\t\t\treturn false;\n                    if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1)\n                        return false;\n                    \n                    // filter by slot IN, OUT types\n                    if(options.do_type_filter && !opts.skipFilter){\n                        var sType = type;\n                        \n                        var sV = sIn.value;\n                        if (opts.inTypeOverride!==false) sV = opts.inTypeOverride;\n\t\t\t\t\t\t//if (sV.toLowerCase() == \"_event_\") sV = LiteGraph.EVENT; // -1\n                        \n                        if(sIn && sV){\n                            //console.log(\"will check filter against \"+sV);\n                            if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored\n                                //console.debug(\"check \"+sType+\" in \"+LiteGraph.registered_slot_in_types[sV].nodes);\n                                var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType);\n                                if (doesInc!==false){\n                                    //console.log(sType+\" HAS \"+sV);\n                                }else{\n                                    /*console.debug(LiteGraph.registered_slot_in_types[sV]);\n                                    console.log(+\" DONT includes \"+type);*/\n                                    return false;\n                                }\n                            }\n                        }\n                        \n                        var sV = sOut.value;\n                        if (opts.outTypeOverride!==false) sV = opts.outTypeOverride;\n                        //if (sV.toLowerCase() == \"_event_\") sV = LiteGraph.EVENT; // -1\n                        \n                        if(sOut && sV){\n                            //console.log(\"search will check filter against \"+sV);\n                            if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored\n                                //console.debug(\"check \"+sType+\" in \"+LiteGraph.registered_slot_out_types[sV].nodes);\n                                var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType);\n                                if (doesInc!==false){\n                                    //console.log(sType+\" HAS \"+sV);\n                                }else{\n                                    /*console.debug(LiteGraph.registered_slot_out_types[sV]);\n                                    console.log(+\" DONT includes \"+type);*/\n                                    return false;\n                                }\n                            }\n                        }\n                    }\n                    return true;\n\t\t\t\t}\n            }\n\n            function addResult(type, className) {\n                var help = document.createElement(\"div\");\n                if (!first) {\n                    first = type;\n                }\n                help.innerText = type;\n                help.dataset[\"type\"] = escape(type);\n                help.className = \"litegraph lite-search-item\";\n                if (className) {\n                    help.className += \" \" + className;\n                }\n                help.addEventListener(\"click\", function(e) {\n                    select(unescape(this.dataset[\"type\"]));\n                });\n                helper.appendChild(help);\n            }\n        }\n\n        return dialog;\n    };\n\n    LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) {\n        if (!node || node.properties[property] === undefined) {\n            return;\n        }\n\n        options = options || {};\n        var that = this;\n\n        var info = node.getPropertyInfo(property);\n\t\tvar type = info.type;\n\n        var input_html = \"\";\n\n        if (type == \"string\" || type == \"number\" || type == \"array\" || type == \"object\") {\n            input_html = \"<input autofocus type='text' class='value'/>\";\n        } else if ( (type == \"enum\" || type == \"combo\") && info.values) {\n            input_html = \"<select autofocus type='text' class='value'>\";\n            for (var i in info.values) {\n                var v = i;\n\t\t\t\tif( info.values.constructor === Array )\n\t\t\t\t\tv = info.values[i];\n\n                input_html +=\n                    \"<option value='\" +\n                    v +\n                    \"' \" +\n                    (v == node.properties[property] ? \"selected\" : \"\") +\n                    \">\" +\n                    info.values[i] +\n                    \"</option>\";\n            }\n            input_html += \"</select>\";\n        } else if (type == \"boolean\" || type == \"toggle\") {\n            input_html =\n                \"<input autofocus type='checkbox' class='value' \" +\n                (node.properties[property] ? \"checked\" : \"\") +\n                \"/>\";\n        } else {\n            console.warn(\"unknown type: \" + type);\n            return;\n        }\n\n        var dialog = this.createDialog(\n            \"<span class='name'>\" +\n                (info.label ? info.label : property) +\n                \"</span>\" +\n                input_html +\n                \"<button>OK</button>\",\n            options\n        );\n\n        var input = false;\n        if ((type == \"enum\" || type == \"combo\") && info.values) {\n            input = dialog.querySelector(\"select\");\n            input.addEventListener(\"change\", function(e) {\n                dialog.modified();\n                setValue(e.target.value);\n                //var index = e.target.value;\n                //setValue( e.options[e.selectedIndex].value );\n            });\n        } else if (type == \"boolean\" || type == \"toggle\") {\n            input = dialog.querySelector(\"input\");\n            if (input) {\n                input.addEventListener(\"click\", function(e) {\n                    dialog.modified();\n                    setValue(!!input.checked);\n                });\n            }\n        } else {\n            input = dialog.querySelector(\"input\");\n            if (input) {\n                input.addEventListener(\"blur\", function(e) {\n                    this.focus();\n                });\n\n\t\t\t\tvar v = node.properties[property] !== undefined ? node.properties[property] : \"\";\n\t\t\t\tif (type !== 'string') {\n                    v = JSON.stringify(v);\n                }\n\n                input.value = v;\n                input.addEventListener(\"keydown\", function(e) {\n                    if (e.keyCode == 27) {\n                        //ESC\n                        dialog.close();\n                    } else if (e.keyCode == 13) {\n                        // ENTER\n                        inner(); // save\n                    } else if (e.keyCode != 13) {\n                        dialog.modified();\n                        return;\n                    }\n                    e.preventDefault();\n                    e.stopPropagation();\n                });\n            }\n        }\n        if (input) input.focus();\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", inner);\n\n        function inner() {\n            setValue(input.value);\n        }\n\n        function setValue(value) {\n\n\t\t\tif(info && info.values && info.values.constructor === Object && info.values[value] != undefined )\n\t\t\t\tvalue = info.values[value];\n\n            if (typeof node.properties[property] == \"number\") {\n                value = Number(value);\n            }\n            if (type == \"array\" || type == \"object\") {\n                value = JSON.parse(value);\n            }\n            node.properties[property] = value;\n            if (node.graph) {\n                node.graph._version++;\n            }\n            if (node.onPropertyChanged) {\n                node.onPropertyChanged(property, value);\n            }\n\t\t\tif(options.onclose)\n\t\t\t\toptions.onclose();\n            dialog.close();\n            node.setDirtyCanvas(true, true);\n        }\n\n\t\treturn dialog;\n    };\n\n    // TODO refactor, theer are different dialog, some uses createDialog, some dont\n    LGraphCanvas.prototype.createDialog = function(html, options) {\n        var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true };\n        options = Object.assign(def_options, options || {});\n\n        var dialog = document.createElement(\"div\");\n        dialog.className = \"graphdialog\";\n        dialog.innerHTML = html;\n        dialog.is_modified = false;\n\n        var rect = this.canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (options.position) {\n            offsetx += options.position[0];\n            offsety += options.position[1];\n        } else if (options.event) {\n            offsetx += options.event.clientX;\n            offsety += options.event.clientY;\n        } //centered\n        else {\n            offsetx += this.canvas.width * 0.5;\n            offsety += this.canvas.height * 0.5;\n        }\n\n        dialog.style.left = offsetx + \"px\";\n        dialog.style.top = offsety + \"px\";\n\n        this.canvas.parentNode.appendChild(dialog);\n        \n        // acheck for input and use default behaviour: save on enter, close on esc\n        if (options.checkForInput){\n            var aI = [];\n            var focused = false;\n            if (aI = dialog.querySelectorAll(\"input\")){\n                aI.forEach(function(iX) {\n                    iX.addEventListener(\"keydown\",function(e){\n                        dialog.modified();\n                        if (e.keyCode == 27) {\n                            dialog.close();\n                        } else if (e.keyCode != 13) {\n                            return;\n                        }\n                        // set value ?\n                        e.preventDefault();\n                        e.stopPropagation();\n                    });\n                    if (!focused) iX.focus();\n                });\n            }\n        }\n        \n        dialog.modified = function(){\n            dialog.is_modified = true;\n        }\n        dialog.close = function() {\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n        \n        var dialogCloseTimer = null;\n        var prevent_timeout = false;\n        dialog.addEventListener(\"mouseleave\", function(e) {\n            if (prevent_timeout)\n                return;\n            if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        dialog.addEventListener(\"mouseenter\", function(e) {\n            if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        var selInDia = dialog.querySelectorAll(\"select\");\n        if (selInDia){\n            // if filtering, check focus changed to comboboxes and prevent closing\n            selInDia.forEach(function(selIn) {\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            });\n        }\n\n        return dialog;\n    };\n\n\tLGraphCanvas.prototype.createPanel = function(title, options) {\n\t\toptions = options || {};\n\n\t\tvar ref_window = options.window || window;\n\t\tvar root = document.createElement(\"div\");\n\t\troot.className = \"litegraph dialog\";\n\t\troot.innerHTML = \"<div class='dialog-header'><span class='dialog-title'></span></div><div class='dialog-content'></div><div style='display:none;' class='dialog-alt-content'></div><div class='dialog-footer'></div>\";\n\t\troot.header = root.querySelector(\".dialog-header\");\n\n\t\tif(options.width)\n\t\t\troot.style.width = options.width + (options.width.constructor === Number ? \"px\" : \"\");\n\t\tif(options.height)\n\t\t\troot.style.height = options.height + (options.height.constructor === Number ? \"px\" : \"\");\n\t\tif(options.closable)\n\t\t{\n\t\t\tvar close = document.createElement(\"span\");\n\t\t\tclose.innerHTML = \"&#10005;\";\n\t\t\tclose.classList.add(\"close\");\n\t\t\tclose.addEventListener(\"click\",function(){\n\t\t\t\troot.close();\n\t\t\t});\n\t\t\troot.header.appendChild(close);\n\t\t}\n\t\troot.title_element = root.querySelector(\".dialog-title\");\n\t\troot.title_element.innerText = title;\n\t\troot.content = root.querySelector(\".dialog-content\");\n        root.alt_content = root.querySelector(\".dialog-alt-content\");\n\t\troot.footer = root.querySelector(\".dialog-footer\");\n\n\t\troot.close = function()\n\t\t{\n\t\t    if (root.onClose && typeof root.onClose == \"function\"){\n\t\t        root.onClose();\n\t\t    }\n            if(root.parentNode)\n\t\t        root.parentNode.removeChild(root);\n\t\t    /* XXX CHECK THIS */\n\t\t    if(this.parentNode){\n\t\t    \tthis.parentNode.removeChild(this);\n\t\t    }\n\t\t    /* XXX this was not working, was fixed with an IF, check this */\n\t\t}\n\n        // function to swap panel content\n        root.toggleAltContent = function(force){\n            if (typeof force != \"undefined\"){\n                var vTo = force ? \"block\" : \"none\";\n                var vAlt = force ? \"none\" : \"block\";\n            }else{\n                var vTo = root.alt_content.style.display != \"block\" ? \"block\" : \"none\";\n                var vAlt = root.alt_content.style.display != \"block\" ? \"none\" : \"block\";\n            }\n            root.alt_content.style.display = vTo;\n            root.content.style.display = vAlt;\n        }\n        \n        root.toggleFooterVisibility = function(force){\n            if (typeof force != \"undefined\"){\n                var vTo = force ? \"block\" : \"none\";\n            }else{\n                var vTo = root.footer.style.display != \"block\" ? \"block\" : \"none\";\n            }\n            root.footer.style.display = vTo;\n        }\n        \n\t\troot.clear = function()\n\t\t{\n\t\t\tthis.content.innerHTML = \"\";\n\t\t}\n\n\t\troot.addHTML = function(code, classname, on_footer)\n\t\t{\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\tif(classname)\n\t\t\t\telem.className = classname;\n\t\t\telem.innerHTML = code;\n\t\t\tif(on_footer)\n\t\t\t\troot.footer.appendChild(elem);\n\t\t\telse\n\t\t\t\troot.content.appendChild(elem);\n\t\t\treturn elem;\n\t\t}\n\n\t\troot.addButton = function( name, callback, options )\n\t\t{\n\t\t\tvar elem = document.createElement(\"button\");\n\t\t\telem.innerText = name;\n\t\t\telem.options = options;\n\t\t\telem.classList.add(\"btn\");\n\t\t\telem.addEventListener(\"click\",callback);\n\t\t\troot.footer.appendChild(elem);\n\t\t\treturn elem;\n\t\t}\n\n\t\troot.addSeparator = function()\n\t\t{\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\telem.className = \"separator\";\n\t\t\troot.content.appendChild(elem);\n\t\t}\n\n\t\troot.addWidget = function( type, name, value, options, callback )\n\t\t{\n\t\t\toptions = options || {};\n\t\t\tvar str_value = String(value);\n\t\t\ttype = type.toLowerCase();\n\t\t\tif(type == \"number\")\n\t\t\t\tstr_value = value.toFixed(3);\n\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\telem.className = \"property\";\n\t\t\telem.innerHTML = \"<span class='property_name'></span><span class='property_value'></span>\";\n\t\t\telem.querySelector(\".property_name\").innerText = options.label || name;\n\t\t\tvar value_element = elem.querySelector(\".property_value\");\n\t\t\tvalue_element.innerText = str_value;\n\t\t\telem.dataset[\"property\"] = name;\n\t\t\telem.dataset[\"type\"] = options.type || type;\n\t\t\telem.options = options;\n\t\t\telem.value = value;\n\n\t\t\tif( type == \"code\" )\n\t\t\t\telem.addEventListener(\"click\", function(e){ root.inner_showCodePad( this.dataset[\"property\"] ); });\n\t\t\telse if (type == \"boolean\")\n\t\t\t{\n\t\t\t\telem.classList.add(\"boolean\");\n\t\t\t\tif(value)\n\t\t\t\t\telem.classList.add(\"bool-on\");\n\t\t\t\telem.addEventListener(\"click\", function(){ \n\t\t\t\t\t//var v = node.properties[this.dataset[\"property\"]]; \n\t\t\t\t\t//node.setProperty(this.dataset[\"property\"],!v); this.innerText = v ? \"true\" : \"false\"; \n\t\t\t\t\tvar propname = this.dataset[\"property\"];\n\t\t\t\t\tthis.value = !this.value;\n\t\t\t\t\tthis.classList.toggle(\"bool-on\");\n\t\t\t\t\tthis.querySelector(\".property_value\").innerText = this.value ? \"true\" : \"false\";\n\t\t\t\t\tinnerChange(propname, this.value );\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if (type == \"string\" || type == \"number\")\n\t\t\t{\n\t\t\t\tvalue_element.setAttribute(\"contenteditable\",true);\n\t\t\t\tvalue_element.addEventListener(\"keydown\", function(e){ \n\t\t\t\t\tif(e.code == \"Enter\" && (type != \"string\" || !e.shiftKey)) // allow for multiline\n\t\t\t\t\t{\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tthis.blur();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tvalue_element.addEventListener(\"blur\", function(){ \n\t\t\t\t\tvar v = this.innerText;\n\t\t\t\t\tvar propname = this.parentNode.dataset[\"property\"];\n\t\t\t\t\tvar proptype = this.parentNode.dataset[\"type\"];\n\t\t\t\t\tif( proptype == \"number\")\n\t\t\t\t\t\tv = Number(v);\n\t\t\t\t\tinnerChange(propname, v);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if (type == \"enum\" || type == \"combo\") {\n\t\t\t\tvar str_value = LGraphCanvas.getPropertyPrintableValue( value, options.values );\n\t\t\t\tvalue_element.innerText = str_value;\n\n\t\t\t\tvalue_element.addEventListener(\"click\", function(event){ \n\t\t\t\t\tvar values = options.values || [];\n\t\t\t\t\tvar propname = this.parentNode.dataset[\"property\"];\n\t\t\t\t\tvar elem_that = this;\n\t\t\t\t\tvar menu = new LiteGraph.ContextMenu(values,{\n\t\t\t\t\t\t\tevent: event,\n\t\t\t\t\t\t\tclassName: \"dark\",\n\t\t\t\t\t\t\tcallback: inner_clicked\n\t\t\t\t\t\t},\n\t\t\t\t\t\tref_window);\n\t\t\t\t\tfunction inner_clicked(v, option, event) {\n\t\t\t\t\t\t//node.setProperty(propname,v); \n\t\t\t\t\t\t//graphcanvas.dirty_canvas = true;\n\t\t\t\t\t\telem_that.innerText = v;\n\t\t\t\t\t\tinnerChange(propname,v);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t});\n            }\n\n\t\t\troot.content.appendChild(elem);\n\n\t\t\tfunction innerChange(name, value)\n\t\t\t{\n\t\t\t\t//console.log(\"change\",name,value);\n\t\t\t\t//that.dirty_canvas = true;\n\t\t\t\tif(options.callback)\n\t\t\t\t\toptions.callback(name,value,options);\n\t\t\t\tif(callback)\n\t\t\t\t\tcallback(name,value,options);\n\t\t\t}\n\n\t\t\treturn elem;\n\t\t}\n\n        if (root.onOpen && typeof root.onOpen == \"function\") root.onOpen();\n        \n\t\treturn root;\n\t};\n\n\tLGraphCanvas.getPropertyPrintableValue = function(value, values)\n\t{\n\t\tif(!values)\n\t\t\treturn String(value);\n\n\t\tif(values.constructor === Array)\n\t\t{\n\t\t\treturn String(value);\t\t\t\n\t\t}\n\n\t\tif(values.constructor === Object)\n\t\t{\n\t\t\tvar desc_value = \"\";\n\t\t\tfor(var k in values)\n\t\t\t{\n\t\t\t\tif(values[k] != value)\n\t\t\t\t\tcontinue;\n\t\t\t\tdesc_value = k;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn String(value) + \" (\"+desc_value+\")\";\n\t\t}\n\t}\n\n    LGraphCanvas.prototype.closePanels = function(){\n        var panel = document.querySelector(\"#node-panel\");\n\t\tif(panel)\n\t\t\tpanel.close();\n        var panel = document.querySelector(\"#option-panel\");\n\t\tif(panel)\n\t\t\tpanel.close();\n    }\n    \n    LGraphCanvas.prototype.showShowGraphOptionsPanel = function(refOpts, obEv, refMenu, refMenu2){\n        if(this.constructor && this.constructor.name == \"HTMLDivElement\"){\n            // assume coming from the menu event click\n            if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){\n                console.warn(\"Canvas not found\"); // need a ref to canvas obj\n                /*console.debug(event);\n                console.debug(event.target);*/\n                return;\n            }\n            var graphcanvas = obEv.event.target.lgraphcanvas;\n        }else{\n            // assume called internally\n            var graphcanvas = this;\n        }\n        graphcanvas.closePanels();\n        var ref_window = graphcanvas.getCanvasWindow();\n        panel = graphcanvas.createPanel(\"Options\",{\n                                            closable: true\n                                            ,window: ref_window\n                                            ,onOpen: function(){\n                                                graphcanvas.OPTIONPANEL_IS_OPEN = true;\n                                            }\n                                            ,onClose: function(){\n                                                graphcanvas.OPTIONPANEL_IS_OPEN = false;\n                                                graphcanvas.options_panel = null;\n                                            }\n                                        });\n        graphcanvas.options_panel = panel;\n        panel.id = \"option-panel\";\n\t\tpanel.classList.add(\"settings\");\n        \n        function inner_refresh(){\n            \n            panel.content.innerHTML = \"\"; //clear\n\n            var fUpdate = function(name, value, options){\n                switch(name){\n                    /*case \"Render mode\":\n                        // Case \"\".. \n                        if (options.values && options.key){\n                            var kV = Object.values(options.values).indexOf(value);\n                            if (kV>=0 && options.values[kV]){\n                                console.debug(\"update graph options: \"+options.key+\": \"+kV);\n                                graphcanvas[options.key] = kV;\n                                //console.debug(graphcanvas);\n                                break;\n                            }\n                        }\n                        console.warn(\"unexpected options\");\n                        console.debug(options);\n                        break;*/\n                    default:\n                        //console.debug(\"want to update graph options: \"+name+\": \"+value);\n                        if (options && options.key){\n                            name = options.key;\n                        }\n                        if (options.values){\n                            value = Object.values(options.values).indexOf(value);\n                        }\n                        //console.debug(\"update graph option: \"+name+\": \"+value);\n                        graphcanvas[name] = value;\n                        break;\n                }\n            };\n            \n            // panel.addWidget( \"string\", \"Graph name\", \"\", {}, fUpdate); // implement\n            \n            var aProps = LiteGraph.availableCanvasOptions;\n            aProps.sort();\n            for(var pI in aProps){\n                var pX = aProps[pI];\n                panel.addWidget( \"boolean\", pX, graphcanvas[pX], {key: pX, on: \"True\", off: \"False\"}, fUpdate);\n            }\n            \n            var aLinks = [ graphcanvas.links_render_mode ];\n            panel.addWidget( \"combo\", \"Render mode\", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: \"links_render_mode\", values: LiteGraph.LINK_RENDER_MODES}, fUpdate);\n            \n            panel.addSeparator();\n            \n            panel.footer.innerHTML = \"\"; // clear\n\n\t\t}\n        inner_refresh();\n\n\t\tgraphcanvas.canvas.parentNode.appendChild( panel );\n    }\n    \n    LGraphCanvas.prototype.showShowNodePanel = function( node )\n\t{\n\t\tthis.SELECTED_NODE = node;\n\t\tthis.closePanels();\n\t\tvar ref_window = this.getCanvasWindow();\n        var that = this;\n\t\tvar graphcanvas = this;\n\t\tvar panel = this.createPanel(node.title || \"\",{\n                                                    closable: true\n                                                    ,window: ref_window\n                                                    ,onOpen: function(){\n                                                        graphcanvas.NODEPANEL_IS_OPEN = true;\n                                                    }\n                                                    ,onClose: function(){\n                                                        graphcanvas.NODEPANEL_IS_OPEN = false;\n                                                        graphcanvas.node_panel = null;\n                                                    }\n                                                });\n        graphcanvas.node_panel = panel;\n\t\tpanel.id = \"node-panel\";\n\t\tpanel.node = node;\n\t\tpanel.classList.add(\"settings\");\n\n\t\tfunction inner_refresh()\n\t\t{\n\t\t\tpanel.content.innerHTML = \"\"; //clear\n\t\t\tpanel.addHTML(\"<span class='node_type'>\"+node.type+\"</span><span class='node_desc'>\"+(node.constructor.desc || \"\")+\"</span><span class='separator'></span>\");\n\n\t\t\tpanel.addHTML(\"<h3>Properties</h3>\");\n\n            var fUpdate = function(name,value){\n                            graphcanvas.graph.beforeChange(node);\n                            switch(name){\n                                case \"Title\":\n                                    node.title = value;\n                                    break;\n                                case \"Mode\":\n                                    var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value);\n                                    if (kV>=0 && LiteGraph.NODE_MODES[kV]){\n                                        node.changeMode(kV);\n                                    }else{\n                                        console.warn(\"unexpected mode: \"+value);\n                                    }\n                                    break;\n                                case \"Color\":\n                                    if (LGraphCanvas.node_colors[value]){\n                                        node.color = LGraphCanvas.node_colors[value].color;\n                                        node.bgcolor = LGraphCanvas.node_colors[value].bgcolor;\n                                    }else{\n                                        console.warn(\"unexpected color: \"+value);\n                                    }\n                                    break;\n                                default:\n                                    node.setProperty(name,value);\n                                    break;\n                            }\n                            graphcanvas.graph.afterChange();\n                            graphcanvas.dirty_canvas = true;\n                        };\n            \n            panel.addWidget( \"string\", \"Title\", node.title, {}, fUpdate);\n            \n            panel.addWidget( \"combo\", \"Mode\", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate);\n            \n            var nodeCol = \"\";\n            if (node.color !== undefined){\n                nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; });\n            }\n            \n            panel.addWidget( \"combo\", \"Color\", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate);\n            \n            for(var pName in node.properties)\n\t\t\t{\n\t\t\t\tvar value = node.properties[pName];\n\t\t\t\tvar info = node.getPropertyInfo(pName);\n\t\t\t\tvar type = info.type || \"string\";\n\n\t\t\t\t//in case the user wants control over the side panel widget\n\t\t\t\tif( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tpanel.addWidget( info.widget || info.type, pName, value, info, fUpdate);\n\t\t\t}\n\n\t\t\tpanel.addSeparator();\n\n\t\t\tif(node.onShowCustomPanelInfo)\n\t\t\t\tnode.onShowCustomPanelInfo(panel);\n\n            panel.footer.innerHTML = \"\"; // clear\n\t\t\tpanel.addButton(\"Delete\",function(){\n\t\t\t\tif(node.block_delete)\n\t\t\t\t\treturn;\n\t\t\t\tnode.graph.remove(node);\n\t\t\t\tpanel.close();\n\t\t\t}).classList.add(\"delete\");\n\t\t}\n\n\t\tpanel.inner_showCodePad = function( propname )\n\t\t{\n            panel.classList.remove(\"settings\");\n            panel.classList.add(\"centered\");\n\n            \n\t\t\t/*if(window.CodeFlask) //disabled for now\n\t\t\t{\n\t\t\t\tpanel.content.innerHTML = \"<div class='code'></div>\";\n\t\t\t\tvar flask = new CodeFlask( \"div.code\", { language: 'js' });\n\t\t\t\tflask.updateCode(node.properties[propname]);\n\t\t\t\tflask.onUpdate( function(code) {\n\t\t\t\t\tnode.setProperty(propname, code);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse\n\t\t\t{*/\n\t\t\t\tpanel.alt_content.innerHTML = \"<textarea class='code'></textarea>\";\n\t\t\t\tvar textarea = panel.alt_content.querySelector(\"textarea\");\n                var fDoneWith = function(){\n                    panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = \"block\"; // panel.close();\n                    panel.toggleFooterVisibility(true);\n                    textarea.parentNode.removeChild(textarea);\n                    panel.classList.add(\"settings\");\n                    panel.classList.remove(\"centered\");\n                    inner_refresh();\n                }\n\t\t\t\ttextarea.value = node.properties[propname];\n\t\t\t\ttextarea.addEventListener(\"keydown\", function(e){\n\t\t\t\t\tif(e.code == \"Enter\" && e.ctrlKey )\n\t\t\t\t\t{\n\t\t\t\t\t\tnode.setProperty(propname, textarea.value);\n                        fDoneWith();\n\t\t\t\t\t}\n\t\t\t\t});\n                panel.toggleAltContent(true);\n                panel.toggleFooterVisibility(false);\n\t\t\t\ttextarea.style.height = \"calc(100% - 40px)\";\n\t\t\t/*}*/\n\t\t\tvar assign = panel.addButton( \"Assign\", function(){\n\t\t\t\tnode.setProperty(propname, textarea.value);\n                fDoneWith();\n\t\t\t});\n\t\t\tpanel.alt_content.appendChild(assign); //panel.content.appendChild(assign);\n\t\t\tvar button = panel.addButton( \"Close\", fDoneWith);\n\t\t\tbutton.style.float = \"right\";\n\t\t\tpanel.alt_content.appendChild(button); // panel.content.appendChild(button);\n\t\t}\n\n\t\tinner_refresh();\n\n\t\tthis.canvas.parentNode.appendChild( panel );\n\t}\n\t\n\tLGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node)\n\t{\n\t\tconsole.log(\"showing subgraph properties dialog\");\n\n\t\tvar old_panel = this.canvas.parentNode.querySelector(\".subgraph_dialog\");\n\t\tif(old_panel)\n\t\t\told_panel.close();\n\n\t\tvar panel = this.createPanel(\"Subgraph Inputs\",{closable:true, width: 500});\n\t\tpanel.node = node;\n\t\tpanel.classList.add(\"subgraph_dialog\");\n\n\t\tfunction inner_refresh()\n\t\t{\n\t\t\tpanel.clear();\n\n\t\t\t//show currents\n\t\t\tif(node.inputs)\n\t\t\t\tfor(var i = 0; i < node.inputs.length; ++i)\n\t\t\t\t{\n\t\t\t\t\tvar input = node.inputs[i];\n\t\t\t\t\tif(input.not_subgraph_input)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tvar html = \"<button>&#10005;</button> <span class='bullet_icon'></span><span class='name'></span><span class='type'></span>\";\n\t\t\t\t\tvar elem = panel.addHTML(html,\"subgraph_property\");\n\t\t\t\t\telem.dataset[\"name\"] = input.name;\n\t\t\t\t\telem.dataset[\"slot\"] = i;\n\t\t\t\t\telem.querySelector(\".name\").innerText = input.name;\n\t\t\t\t\telem.querySelector(\".type\").innerText = input.type;\n\t\t\t\t\telem.querySelector(\"button\").addEventListener(\"click\",function(e){\n\t\t\t\t\t\tnode.removeInput( Number( this.parentNode.dataset[\"slot\"] ) );\n\t\t\t\t\t\tinner_refresh();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\n\t\t//add extra\n\t\tvar html = \" + <span class='label'>Name</span><input class='name'/><span class='label'>Type</span><input class='type'></input><button>+</button>\";\n\t\tvar elem = panel.addHTML(html,\"subgraph_property extra\", true);\n\t\telem.querySelector(\"button\").addEventListener(\"click\", function(e){\n\t\t\tvar elem = this.parentNode;\n\t\t\tvar name = elem.querySelector(\".name\").value;\n\t\t\tvar type = elem.querySelector(\".type\").value;\n\t\t\tif(!name || node.findInputSlot(name) != -1)\n\t\t\t\treturn;\n\t\t\tnode.addInput(name,type);\n\t\t\telem.querySelector(\".name\").value = \"\";\n\t\t\telem.querySelector(\".type\").value = \"\";\n\t\t\tinner_refresh();\n\t\t});\n\n\t\tinner_refresh();\n\t    this.canvas.parentNode.appendChild(panel);\n\t\treturn panel;\n\t}\n    LGraphCanvas.prototype.showSubgraphPropertiesDialogRight = function (node) {\n\n        // console.log(\"showing subgraph properties dialog\");\n        var that = this;\n        // old_panel if old_panel is exist close it\n        var old_panel = this.canvas.parentNode.querySelector(\".subgraph_dialog\");\n        if (old_panel)\n            old_panel.close();\n        // new panel\n        var panel = this.createPanel(\"Subgraph Outputs\", { closable: true, width: 500 });\n        panel.node = node;\n        panel.classList.add(\"subgraph_dialog\");\n\n        function inner_refresh() {\n            panel.clear();\n            //show currents\n            if (node.outputs)\n                for (var i = 0; i < node.outputs.length; ++i) {\n                    var input = node.outputs[i];\n                    if (input.not_subgraph_output)\n                        continue;\n                    var html = \"<button>&#10005;</button> <span class='bullet_icon'></span><span class='name'></span><span class='type'></span>\";\n                    var elem = panel.addHTML(html, \"subgraph_property\");\n                    elem.dataset[\"name\"] = input.name;\n                    elem.dataset[\"slot\"] = i;\n                    elem.querySelector(\".name\").innerText = input.name;\n                    elem.querySelector(\".type\").innerText = input.type;\n                    elem.querySelector(\"button\").addEventListener(\"click\", function (e) {\n                        node.removeOutput(Number(this.parentNode.dataset[\"slot\"]));\n                        inner_refresh();\n                    });\n                }\n        }\n\n        //add extra\n        var html = \" + <span class='label'>Name</span><input class='name'/><span class='label'>Type</span><input class='type'></input><button>+</button>\";\n        var elem = panel.addHTML(html, \"subgraph_property extra\", true);\n        elem.querySelector(\".name\").addEventListener(\"keydown\", function (e) {\n            if (e.keyCode == 13) {\n                addOutput.apply(this)\n            }\n        })\n        elem.querySelector(\"button\").addEventListener(\"click\", function (e) {\n            addOutput.apply(this)\n        });\n        function addOutput() {\n            var elem = this.parentNode;\n            var name = elem.querySelector(\".name\").value;\n            var type = elem.querySelector(\".type\").value;\n            if (!name || node.findOutputSlot(name) != -1)\n                return;\n            node.addOutput(name, type);\n            elem.querySelector(\".name\").value = \"\";\n            elem.querySelector(\".type\").value = \"\";\n            inner_refresh();\n        }\n\n        inner_refresh();\n        this.canvas.parentNode.appendChild(panel);\n        return panel;\n    }\n\tLGraphCanvas.prototype.checkPanels = function()\n\t{\n\t\tif(!this.canvas)\n\t\t\treturn;\n\t\tvar panels = this.canvas.parentNode.querySelectorAll(\".litegraph.dialog\");\n\t\tfor(var i = 0; i < panels.length; ++i)\n\t\t{\n\t\t\tvar panel = panels[i];\n\t\t\tif( !panel.node )\n\t\t\t\tcontinue;\n\t\t\tif( !panel.node.graph || panel.graph != this.graph )\n\t\t\t\tpanel.close();\n\t\t}\n\t}\n\n    LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) {\n\t\tnode.graph.beforeChange(/*?*/);\n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tnode.collapse();\n\t\t}\n\t\t\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tnode.graph.afterChange(/*?*/);\n    };\n\n    LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) {\n        node.pin();\n    };\n\n    LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) {\n        new LiteGraph.ContextMenu(\n            LiteGraph.NODE_MODES,\n            { event: e, callback: inner_clicked, parentMenu: menu, node: node }\n        );\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n            var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v);\n            var fApplyMultiNode = function(node){\n\t\t\t\tif (kV>=0 && LiteGraph.NODE_MODES[kV])\n\t\t\t\t\tnode.changeMode(kV);\n\t\t\t\telse{\n\t\t\t\t\tconsole.warn(\"unexpected mode: \"+v);\n\t\t\t\t\tnode.changeMode(LiteGraph.ALWAYS);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyMultiNode(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node for color\";\n        }\n\n        var values = [];\n        values.push({\n            value: null,\n            content:\n                \"<span style='display: block; padding-left: 4px;'>No color</span>\"\n        });\n\n        for (var i in LGraphCanvas.node_colors) {\n            var color = LGraphCanvas.node_colors[i];\n            var value = {\n                value: i,\n                content:\n                    \"<span style='display: block; color: #999; padding-left: 4px; border-left: 8px solid \" +\n                    color.color +\n                    \"; background-color:\" +\n                    color.bgcolor +\n                    \"'>\" +\n                    i +\n                    \"</span>\"\n            };\n            values.push(value);\n        }\n        new LiteGraph.ContextMenu(values, {\n            event: e,\n            callback: inner_clicked,\n            parentMenu: menu,\n            node: node\n        });\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n\n            var color = v.value ? LGraphCanvas.node_colors[v.value] : null;\n\t\t\t\n\t\t\tvar fApplyColor = function(node){\n\t\t\t\tif (color) {\n\t\t\t\t\tif (node.constructor === LiteGraph.LGraphGroup) {\n\t\t\t\t\t\tnode.color = color.groupcolor;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnode.color = color.color;\n\t\t\t\t\t\tnode.bgcolor = color.bgcolor;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tdelete node.color;\n\t\t\t\t\tdelete node.bgcolor;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyColor(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyColor(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n            node.setDirtyCanvas(true, true);\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node passed\";\n        }\n\n        new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, {\n            event: e,\n            callback: inner_clicked,\n            parentMenu: menu,\n            node: node\n        });\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n\t\t\tnode.graph.beforeChange(/*?*/); //node\n            \n\t\t\tvar fApplyMultiNode = function(node){\n\t\t\t\tnode.shape = v;\n\t\t\t}\n\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyMultiNode(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tnode.graph.afterChange(/*?*/); //node\n            node.setDirtyCanvas(true);\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node passed\";\n        }\n\n\t\tvar graph = node.graph;\n\t\tgraph.beforeChange();\n        \n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tif (node.removable === false) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tgraph.remove(node);\n\t\t}\n\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tgraph.afterChange();\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) {\n\t\tvar graph = node.graph;\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif(!graphcanvas) //??\n\t\t\treturn;\n\n\t\tvar nodes_list = Object.values( graphcanvas.selected_nodes || {} );\n\t\tif( !nodes_list.length )\n\t\t\tnodes_list = [ node ];\n\n\t\tvar subgraph_node = LiteGraph.createNode(\"graph/subgraph\");\n\t\tsubgraph_node.pos = node.pos.concat();\n\t\tgraph.add(subgraph_node);\n\n\t\tsubgraph_node.buildFromNodes( nodes_list );\n\n\t\tgraphcanvas.deselectAllNodes();\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) {\n        \n\t\tnode.graph.beforeChange();\n        \n\t\tvar newSelected = {};\n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tif (node.clonable === false) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar newnode = node.clone();\n\t\t\tif (!newnode) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tnewnode.pos = [node.pos[0] + 5, node.pos[1] + 5];\n\t\t\tnode.graph.add(newnode);\n\t\t\tnewSelected[newnode.id] = newnode;\n\t\t}\n\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tif(Object.keys(newSelected).length){\n\t\t\tgraphcanvas.selectNodes(newSelected);\n\t\t}\n\t\t\n\t\tnode.graph.afterChange();\n\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.node_colors = {\n        red: { color: \"#322\", bgcolor: \"#533\", groupcolor: \"#A88\" },\n        brown: { color: \"#332922\", bgcolor: \"#593930\", groupcolor: \"#b06634\" },\n        green: { color: \"#232\", bgcolor: \"#353\", groupcolor: \"#8A8\" },\n        blue: { color: \"#223\", bgcolor: \"#335\", groupcolor: \"#88A\" },\n        pale_blue: {\n            color: \"#2a363b\",\n            bgcolor: \"#3f5159\",\n            groupcolor: \"#3f789e\"\n        },\n        cyan: { color: \"#233\", bgcolor: \"#355\", groupcolor: \"#8AA\" },\n        purple: { color: \"#323\", bgcolor: \"#535\", groupcolor: \"#a1309b\" },\n        yellow: { color: \"#432\", bgcolor: \"#653\", groupcolor: \"#b58b2a\" },\n        black: { color: \"#222\", bgcolor: \"#000\", groupcolor: \"#444\" }\n    };\n\n    LGraphCanvas.prototype.getCanvasMenuOptions = function() {\n        var options = null;\n\t\tvar that = this;\n        if (this.getMenuOptions) {\n            options = this.getMenuOptions();\n        } else {\n            options = [\n                {\n                    content: \"Add Node\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuAdd\n                },\n                { content: \"Add Group\", callback: LGraphCanvas.onGroupAdd },\n\t\t\t\t//{ content: \"Arrange\", callback: that.graph.arrange },\n                //{content:\"Collapse All\", callback: LGraphCanvas.onMenuCollapseAll }\n            ];\n            /*if (LiteGraph.showCanvasOptions){\n                options.push({ content: \"Options\", callback: that.showShowGraphOptionsPanel });\n            }*/\n\n            if (Object.keys(this.selected_nodes).length > 1) {\n                options.push({\n                    content: \"Align\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onGroupAlign,\n                })\n            }\n\n            if (this._graph_stack && this._graph_stack.length > 0) {\n                options.push(null, {\n                    content: \"Close subgraph\",\n                    callback: this.closeSubgraph.bind(this)\n                });\n            }\n        }\n\n        if (this.getExtraMenuOptions) {\n            var extra = this.getExtraMenuOptions(this, options);\n            if (extra) {\n                options = options.concat(extra);\n            }\n        }\n\n        return options;\n    };\n\n    //called by processContextMenu to extract the menu list\n    LGraphCanvas.prototype.getNodeMenuOptions = function(node) {\n        var options = null;\n\n        if (node.getMenuOptions) {\n            options = node.getMenuOptions(this);\n        } else {\n            options = [\n                {\n                    content: \"Inputs\",\n                    has_submenu: true,\n                    disabled: true,\n                    callback: LGraphCanvas.showMenuNodeOptionalInputs\n                },\n                {\n                    content: \"Outputs\",\n                    has_submenu: true,\n                    disabled: true,\n                    callback: LGraphCanvas.showMenuNodeOptionalOutputs\n                },\n                null,\n                {\n                    content: \"Properties\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onShowMenuNodeProperties\n                },\n                null,\n                {\n                    content: \"Title\",\n                    callback: LGraphCanvas.onShowPropertyEditor\n                },\n                {\n                    content: \"Mode\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeMode\n                }];\n            if(node.resizable !== false){\n                options.push({\n                    content: \"Resize\", callback: LGraphCanvas.onMenuResizeNode\n                });\n            }\n            options.push(\n                {\n                    content: \"Collapse\",\n                    callback: LGraphCanvas.onMenuNodeCollapse\n                },\n                { content: \"Pin\", callback: LGraphCanvas.onMenuNodePin },\n                {\n                    content: \"Colors\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeColors\n                },\n                {\n                    content: \"Shapes\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeShapes\n                },\n                null\n            );\n        }\n\n        if (node.onGetInputs) {\n            var inputs = node.onGetInputs();\n            if (inputs && inputs.length) {\n                options[0].disabled = false;\n            }\n        }\n\n        if (node.onGetOutputs) {\n            var outputs = node.onGetOutputs();\n            if (outputs && outputs.length) {\n                options[1].disabled = false;\n            }\n        }\n\n        if (node.getExtraMenuOptions) {\n            var extra = node.getExtraMenuOptions(this, options);\n            if (extra) {\n                extra.push(null);\n                options = extra.concat(options);\n            }\n        }\n\n        if (node.clonable !== false) {\n            options.push({\n                content: \"Clone\",\n                callback: LGraphCanvas.onMenuNodeClone\n            });\n        }\n\n\t\tif(0) //TODO\n\t\toptions.push({\n\t\t\tcontent: \"To Subgraph\",\n\t\t\tcallback: LGraphCanvas.onMenuNodeToSubgraph\n\t\t});\n\n        if (Object.keys(this.selected_nodes).length > 1) {\n            options.push({\n                content: \"Align Selected To\",\n                has_submenu: true,\n                callback: LGraphCanvas.onNodeAlign,\n            })\n        }\n\n\t\toptions.push(null, {\n\t\t\tcontent: \"Remove\",\n\t\t\tdisabled: !(node.removable !== false && !node.block_delete ),\n\t\t\tcallback: LGraphCanvas.onMenuNodeRemove\n\t\t});\n\n        if (node.graph && node.graph.onGetNodeMenuOptions) {\n            node.graph.onGetNodeMenuOptions(options, node);\n        }\n\n        return options;\n    };\n\n    LGraphCanvas.prototype.getGroupMenuOptions = function(node) {\n        var o = [\n            { content: \"Title\", callback: LGraphCanvas.onShowPropertyEditor },\n            {\n                content: \"Color\",\n                has_submenu: true,\n                callback: LGraphCanvas.onMenuNodeColors\n            },\n            {\n                content: \"Font size\",\n                property: \"font_size\",\n                type: \"Number\",\n                callback: LGraphCanvas.onShowPropertyEditor\n            },\n            null,\n            { content: \"Remove\", callback: LGraphCanvas.onMenuNodeRemove }\n        ];\n\n        return o;\n    };\n\n    LGraphCanvas.prototype.processContextMenu = function(node, event) {\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var menu_info = null;\n        var options = {\n            event: event,\n            callback: inner_option_clicked,\n            extra: node\n        };\n\n\t\tif(node)\n\t\t\toptions.title = node.type;\n\n        //check if mouse is in input\n        var slot = null;\n        if (node) {\n            slot = node.getSlotInPosition(event.canvasX, event.canvasY);\n            LGraphCanvas.active_node = node;\n        }\n\n        if (slot) {\n            //on slot\n            menu_info = [];\n            if (node.getSlotMenuOptions) {\n                menu_info = node.getSlotMenuOptions(slot);\n            } else {\n                if (\n                    slot &&\n                    slot.output &&\n                    slot.output.links &&\n                    slot.output.links.length\n                ) {\n                    menu_info.push({ content: \"Disconnect Links\", slot: slot });\n                }\n                var _slot = slot.input || slot.output;\n                if (_slot.removable){\n                \tmenu_info.push(\n\t                    _slot.locked\n\t                        ? \"Cannot remove\"\n\t                        : { content: \"Remove Slot\", slot: slot }\n\t                );\n            \t}\n                if (!_slot.nameLocked){\n\t                menu_info.push({ content: \"Rename Slot\", slot: slot });\n                }\n    \n            }\n            options.title =\n                (slot.input ? slot.input.type : slot.output.type) || \"*\";\n            if (slot.input && slot.input.type == LiteGraph.ACTION) {\n                options.title = \"Action\";\n            }\n            if (slot.output && slot.output.type == LiteGraph.EVENT) {\n                options.title = \"Event\";\n            }\n        } else {\n            if (node) {\n                //on node\n                menu_info = this.getNodeMenuOptions(node);\n            } else {\n                menu_info = this.getCanvasMenuOptions();\n                var group = this.graph.getGroupOnPos(\n                    event.canvasX,\n                    event.canvasY\n                );\n                if (group) {\n                    //on group\n                    menu_info.push(null, {\n                        content: \"Edit Group\",\n                        has_submenu: true,\n                        submenu: {\n                            title: \"Group\",\n                            extra: group,\n                            options: this.getGroupMenuOptions(group)\n                        }\n                    });\n                }\n            }\n        }\n\n        //show menu\n        if (!menu_info) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window);\n\n        function inner_option_clicked(v, options, e) {\n            if (!v) {\n                return;\n            }\n\n            if (v.content == \"Remove Slot\") {\n                var info = v.slot;\n                node.graph.beforeChange();\n                if (info.input) {\n                    node.removeInput(info.slot);\n                } else if (info.output) {\n                    node.removeOutput(info.slot);\n                }\n                node.graph.afterChange();\n                return;\n            } else if (v.content == \"Disconnect Links\") {\n                var info = v.slot;\n                node.graph.beforeChange();\n                if (info.output) {\n                    node.disconnectOutput(info.slot);\n                } else if (info.input) {\n                    node.disconnectInput(info.slot);\n                }\n                node.graph.afterChange();\n                return;\n            } else if (v.content == \"Rename Slot\") {\n                var info = v.slot;\n                var slot_info = info.input\n                    ? node.getInputInfo(info.slot)\n                    : node.getOutputInfo(info.slot);\n                var dialog = that.createDialog(\n                    \"<span class='name'>Name</span><input autofocus type='text'/><button>OK</button>\",\n                    options\n                );\n                var input = dialog.querySelector(\"input\");\n                if (input && slot_info) {\n                    input.value = slot_info.label || \"\";\n                }\n                var inner = function(){\n                \tnode.graph.beforeChange();\n                    if (input.value) {\n                        if (slot_info) {\n                            slot_info.label = input.value;\n                        }\n                        that.setDirty(true);\n                    }\n                    dialog.close();\n                    node.graph.afterChange();\n                }\n                dialog.querySelector(\"button\").addEventListener(\"click\", inner);\n                input.addEventListener(\"keydown\", function(e) {\n                    dialog.is_modified = true;\n                    if (e.keyCode == 27) {\n                        //ESC\n                        dialog.close();\n                    } else if (e.keyCode == 13) {\n                        inner(); // save\n                    } else if (e.keyCode != 13 && e.target.localName != \"textarea\") {\n                        return;\n                    }\n                    e.preventDefault();\n                    e.stopPropagation();\n                });\n                input.focus();\n            }\n\n            //if(v.callback)\n            //\treturn v.callback.call(that, node, options, e, menu, that, event );\n        }\n    };\n\n    //API *************************************************\n    function compareObjects(a, b) {\n        for (var i in a) {\n            if (a[i] != b[i]) {\n                return false;\n            }\n        }\n        return true;\n    }\n    LiteGraph.compareObjects = compareObjects;\n\n    function distance(a, b) {\n        return Math.sqrt(\n            (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])\n        );\n    }\n    LiteGraph.distance = distance;\n\n    function colorToString(c) {\n        return (\n            \"rgba(\" +\n            Math.round(c[0] * 255).toFixed() +\n            \",\" +\n            Math.round(c[1] * 255).toFixed() +\n            \",\" +\n            Math.round(c[2] * 255).toFixed() +\n            \",\" +\n            (c.length == 4 ? c[3].toFixed(2) : \"1.0\") +\n            \")\"\n        );\n    }\n    LiteGraph.colorToString = colorToString;\n\n    function isInsideRectangle(x, y, left, top, width, height) {\n        if (left < x && left + width > x && top < y && top + height > y) {\n            return true;\n        }\n        return false;\n    }\n    LiteGraph.isInsideRectangle = isInsideRectangle;\n\n    //[minx,miny,maxx,maxy]\n    function growBounding(bounding, x, y) {\n        if (x < bounding[0]) {\n            bounding[0] = x;\n        } else if (x > bounding[2]) {\n            bounding[2] = x;\n        }\n\n        if (y < bounding[1]) {\n            bounding[1] = y;\n        } else if (y > bounding[3]) {\n            bounding[3] = y;\n        }\n    }\n    LiteGraph.growBounding = growBounding;\n\n    //point inside bounding box\n    function isInsideBounding(p, bb) {\n        if (\n            p[0] < bb[0][0] ||\n            p[1] < bb[0][1] ||\n            p[0] > bb[1][0] ||\n            p[1] > bb[1][1]\n        ) {\n            return false;\n        }\n        return true;\n    }\n    LiteGraph.isInsideBounding = isInsideBounding;\n\n    //bounding overlap, format: [ startx, starty, width, height ]\n    function overlapBounding(a, b) {\n        var A_end_x = a[0] + a[2];\n        var A_end_y = a[1] + a[3];\n        var B_end_x = b[0] + b[2];\n        var B_end_y = b[1] + b[3];\n\n        if (\n            a[0] > B_end_x ||\n            a[1] > B_end_y ||\n            A_end_x < b[0] ||\n            A_end_y < b[1]\n        ) {\n            return false;\n        }\n        return true;\n    }\n    LiteGraph.overlapBounding = overlapBounding;\n\n    //Convert a hex value to its decimal value - the inputted hex must be in the\n    //\tformat of a hex triplet - the kind we use for HTML colours. The function\n    //\twill return an array with three values.\n    function hex2num(hex) {\n        if (hex.charAt(0) == \"#\") {\n            hex = hex.slice(1);\n        } //Remove the '#' char - if there is one.\n        hex = hex.toUpperCase();\n        var hex_alphabets = \"0123456789ABCDEF\";\n        var value = new Array(3);\n        var k = 0;\n        var int1, int2;\n        for (var i = 0; i < 6; i += 2) {\n            int1 = hex_alphabets.indexOf(hex.charAt(i));\n            int2 = hex_alphabets.indexOf(hex.charAt(i + 1));\n            value[k] = int1 * 16 + int2;\n            k++;\n        }\n        return value;\n    }\n\n    LiteGraph.hex2num = hex2num;\n\n    //Give a array with three values as the argument and the function will return\n    //\tthe corresponding hex triplet.\n    function num2hex(triplet) {\n        var hex_alphabets = \"0123456789ABCDEF\";\n        var hex = \"#\";\n        var int1, int2;\n        for (var i = 0; i < 3; i++) {\n            int1 = triplet[i] / 16;\n            int2 = triplet[i] % 16;\n\n            hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2);\n        }\n        return hex;\n    }\n\n    LiteGraph.num2hex = num2hex;\n\n    /* LiteGraph GUI elements used for canvas editing *************************************/\n\n    /**\n     * ContextMenu from LiteGUI\n     *\n     * @class ContextMenu\n     * @constructor\n     * @param {Array} values (allows object { title: \"Nice text\", callback: function ... })\n     * @param {Object} options [optional] Some options:\\\n     * - title: title to show on top of the menu\n     * - callback: function to call when an option is clicked, it receives the item information\n     * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback\n     * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position\n     */\n    function ContextMenu(values, options) {\n        options = options || {};\n        this.options = options;\n        var that = this;\n\n        //to link a menu with its parent\n        if (options.parentMenu) {\n            if (options.parentMenu.constructor !== this.constructor) {\n                console.error(\n                    \"parentMenu must be of class ContextMenu, ignoring it\"\n                );\n                options.parentMenu = null;\n            } else {\n                this.parentMenu = options.parentMenu;\n                this.parentMenu.lock = true;\n                this.parentMenu.current_submenu = this;\n            }\n        }\n\n\t\tvar eventClass = null;\n\t\tif(options.event) //use strings because comparing classes between windows doesnt work\n\t\t\teventClass = options.event.constructor.name;\n        if ( eventClass !== \"MouseEvent\" &&\n            eventClass !== \"CustomEvent\" &&\n\t\t\teventClass !== \"PointerEvent\"\n        ) {\n            console.error(\n                \"Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. (\"+eventClass+\")\"\n            );\n            options.event = null;\n        }\n\n        var root = document.createElement(\"div\");\n        root.className = \"litegraph litecontextmenu litemenubar-panel\";\n        if (options.className) {\n            root.className += \" \" + options.className;\n        }\n        root.style.minWidth = 100;\n        root.style.minHeight = 100;\n        root.style.pointerEvents = \"none\";\n        setTimeout(function() {\n            root.style.pointerEvents = \"auto\";\n        }, 100); //delay so the mouse up event is not caught by this element\n\n        //this prevents the default context browser menu to open in case this menu was created when pressing right button\n\t\tLiteGraph.pointerListenerAdd(root,\"up\",\n            function(e) {\n\t\t\t  \t//console.log(\"pointerevents: ContextMenu up root prevent\");\n                e.preventDefault();\n                return true;\n            },\n            true\n        );\n        root.addEventListener(\n            \"contextmenu\",\n            function(e) {\n                if (e.button != 2) {\n                    //right button\n                    return false;\n                }\n                e.preventDefault();\n                return false;\n            },\n            true\n        );\n\n        LiteGraph.pointerListenerAdd(root,\"down\",\n            function(e) {\n\t\t\t  \t//console.log(\"pointerevents: ContextMenu down\");\n                if (e.button == 2) {\n                    that.close();\n                    e.preventDefault();\n                    return true;\n                }\n            },\n            true\n        );\n\n        function on_mouse_wheel(e) {\n            var pos = parseInt(root.style.top);\n            root.style.top =\n                (pos + e.deltaY * options.scroll_speed).toFixed() + \"px\";\n            e.preventDefault();\n            return true;\n        }\n\n        if (!options.scroll_speed) {\n            options.scroll_speed = 0.1;\n        }\n\n        root.addEventListener(\"wheel\", on_mouse_wheel, true);\n        root.addEventListener(\"mousewheel\", on_mouse_wheel, true);\n\n        this.root = root;\n\n        //title\n        if (options.title) {\n            var element = document.createElement(\"div\");\n            element.className = \"litemenu-title\";\n            element.innerHTML = options.title;\n            root.appendChild(element);\n        }\n\n        //entries\n        var num = 0;\n        for (var i=0; i < values.length; i++) {\n            var name = values.constructor == Array ? values[i] : i;\n            if (name != null && name.constructor !== String) {\n                name = name.content === undefined ? String(name) : name.content;\n            }\n            var value = values[i];\n            this.addItem(name, value, options);\n            num++;\n        }\n\n        //close on leave? touch enabled devices won't work TODO use a global device detector and condition on that\n        /*LiteGraph.pointerListenerAdd(root,\"leave\", function(e) {\n\t\t  \tconsole.log(\"pointerevents: ContextMenu leave\");\n            if (that.lock) {\n                return;\n            }\n            if (root.closing_timer) {\n                clearTimeout(root.closing_timer);\n            }\n            root.closing_timer = setTimeout(that.close.bind(that, e), 500);\n            //that.close(e);\n        });*/\n\n\t\tLiteGraph.pointerListenerAdd(root,\"enter\", function(e) {\n\t\t  \t//console.log(\"pointerevents: ContextMenu enter\");\n            if (root.closing_timer) {\n                clearTimeout(root.closing_timer);\n            }\n        });\n\n        //insert before checking position\n        var root_document = document;\n        if (options.event) {\n            root_document = options.event.target.ownerDocument;\n        }\n\n        if (!root_document) {\n            root_document = document;\n        }\n\n\t\tif( root_document.fullscreenElement )\n\t        root_document.fullscreenElement.appendChild(root);\n\t\telse\n\t\t    root_document.body.appendChild(root);\n\n        //compute best position\n        var left = options.left || 0;\n        var top = options.top || 0;\n        if (options.event) {\n            left = options.event.clientX - 10;\n            top = options.event.clientY - 10;\n            if (options.title) {\n                top -= 20;\n            }\n\n            if (options.parentMenu) {\n                var rect = options.parentMenu.root.getBoundingClientRect();\n                left = rect.left + rect.width;\n            }\n\n            var body_rect = document.body.getBoundingClientRect();\n            var root_rect = root.getBoundingClientRect();\n\t\t\tif(body_rect.height == 0)\n\t\t\t\tconsole.error(\"document.body height is 0. That is dangerous, set html,body { height: 100%; }\");\n\n            if (body_rect.width && left > body_rect.width - root_rect.width - 10) {\n                left = body_rect.width - root_rect.width - 10;\n            }\n            if (body_rect.height && top > body_rect.height - root_rect.height - 10) {\n                top = body_rect.height - root_rect.height - 10;\n            }\n        }\n\n        root.style.left = left + \"px\";\n        root.style.top = top + \"px\";\n\n        if (options.scale) {\n            root.style.transform = \"scale(\" + options.scale + \")\";\n        }\n    }\n\n    ContextMenu.prototype.addItem = function(name, value, options) {\n        var that = this;\n        options = options || {};\n\n        var element = document.createElement(\"div\");\n        element.className = \"litemenu-entry submenu\";\n\n        var disabled = false;\n\n        if (value === null) {\n            element.classList.add(\"separator\");\n            //element.innerHTML = \"<hr/>\"\n            //continue;\n        } else {\n            element.innerHTML = value && value.title ? value.title : name;\n            element.value = value;\n\n            if (value) {\n                if (value.disabled) {\n                    disabled = true;\n                    element.classList.add(\"disabled\");\n                }\n                if (value.submenu || value.has_submenu) {\n                    element.classList.add(\"has_submenu\");\n                }\n            }\n\n            if (typeof value == \"function\") {\n                element.dataset[\"value\"] = name;\n                element.onclick_callback = value;\n            } else {\n                element.dataset[\"value\"] = value;\n            }\n\n            if (value.className) {\n                element.className += \" \" + value.className;\n            }\n        }\n\n        this.root.appendChild(element);\n        if (!disabled) {\n            element.addEventListener(\"click\", inner_onclick);\n        }\n        if (!disabled && options.autoopen) {\n\t\t\tLiteGraph.pointerListenerAdd(element,\"enter\",inner_over);\n        }\n\n        function inner_over(e) {\n            var value = this.value;\n            if (!value || !value.has_submenu) {\n                return;\n            }\n            //if it is a submenu, autoopen like the item was clicked\n            inner_onclick.call(this, e);\n        }\n\n        //menu option clicked\n        function inner_onclick(e) {\n            var value = this.value;\n            var close_parent = true;\n\n            if (that.current_submenu) {\n                that.current_submenu.close(e);\n            }\n\n            //global callback\n            if (options.callback) {\n                var r = options.callback.call(\n                    this,\n                    value,\n                    options,\n                    e,\n                    that,\n                    options.node\n                );\n                if (r === true) {\n                    close_parent = false;\n                }\n            }\n\n            //special cases\n            if (value) {\n                if (\n                    value.callback &&\n                    !options.ignore_item_callbacks &&\n                    value.disabled !== true\n                ) {\n                    //item callback\n                    var r = value.callback.call(\n                        this,\n                        value,\n                        options,\n                        e,\n                        that,\n                        options.extra\n                    );\n                    if (r === true) {\n                        close_parent = false;\n                    }\n                }\n                if (value.submenu) {\n                    if (!value.submenu.options) {\n                        throw \"ContextMenu submenu needs options\";\n                    }\n                    var submenu = new that.constructor(value.submenu.options, {\n                        callback: value.submenu.callback,\n                        event: e,\n                        parentMenu: that,\n                        ignore_item_callbacks:\n                            value.submenu.ignore_item_callbacks,\n                        title: value.submenu.title,\n                        extra: value.submenu.extra,\n                        autoopen: options.autoopen\n                    });\n                    close_parent = false;\n                }\n            }\n\n            if (close_parent && !that.lock) {\n                that.close();\n            }\n        }\n\n        return element;\n    };\n\n    ContextMenu.prototype.close = function(e, ignore_parent_menu) {\n        if (this.root.parentNode) {\n            this.root.parentNode.removeChild(this.root);\n        }\n        if (this.parentMenu && !ignore_parent_menu) {\n            this.parentMenu.lock = false;\n            this.parentMenu.current_submenu = null;\n            if (e === undefined) {\n                this.parentMenu.close();\n            } else if (\n                e &&\n                !ContextMenu.isCursorOverElement(e, this.parentMenu.root)\n            ) {\n                ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method+\"leave\", e);\n            }\n        }\n        if (this.current_submenu) {\n            this.current_submenu.close(e, true);\n        }\n\n        if (this.root.closing_timer) {\n            clearTimeout(this.root.closing_timer);\n        }\n        \n        // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu\n        // on key press, allow filtering/selecting the context menu elements\n    };\n\n    //this code is used to trigger events easily (used in the context menu mouseleave\n    ContextMenu.trigger = function(element, event_name, params, origin) {\n        var evt = document.createEvent(\"CustomEvent\");\n        evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail\n        evt.srcElement = origin;\n        if (element.dispatchEvent) {\n            element.dispatchEvent(evt);\n        } else if (element.__events) {\n            element.__events.dispatchEvent(evt);\n        }\n        //else nothing seems binded here so nothing to do\n        return evt;\n    };\n\n    //returns the top most menu\n    ContextMenu.prototype.getTopMenu = function() {\n        if (this.options.parentMenu) {\n            return this.options.parentMenu.getTopMenu();\n        }\n        return this;\n    };\n\n    ContextMenu.prototype.getFirstEvent = function() {\n        if (this.options.parentMenu) {\n            return this.options.parentMenu.getFirstEvent();\n        }\n        return this.options.event;\n    };\n\n    ContextMenu.isCursorOverElement = function(event, element) {\n        var left = event.clientX;\n        var top = event.clientY;\n        var rect = element.getBoundingClientRect();\n        if (!rect) {\n            return false;\n        }\n        if (\n            top > rect.top &&\n            top < rect.top + rect.height &&\n            left > rect.left &&\n            left < rect.left + rect.width\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    LiteGraph.ContextMenu = ContextMenu;\n\n    LiteGraph.closeAllContextMenus = function(ref_window) {\n        ref_window = ref_window || window;\n\n        var elements = ref_window.document.querySelectorAll(\".litecontextmenu\");\n        if (!elements.length) {\n            return;\n        }\n\n        var result = [];\n        for (var i = 0; i < elements.length; i++) {\n            result.push(elements[i]);\n        }\n\n        for (var i=0; i < result.length; i++) {\n            if (result[i].close) {\n                result[i].close();\n            } else if (result[i].parentNode) {\n                result[i].parentNode.removeChild(result[i]);\n            }\n        }\n    };\n\n    LiteGraph.extendClass = function(target, origin) {\n        for (var i in origin) {\n            //copy class properties\n            if (target.hasOwnProperty(i)) {\n                continue;\n            }\n            target[i] = origin[i];\n        }\n\n        if (origin.prototype) {\n            //copy prototype properties\n            for (var i in origin.prototype) {\n                //only enumerable\n                if (!origin.prototype.hasOwnProperty(i)) {\n                    continue;\n                }\n\n                if (target.prototype.hasOwnProperty(i)) {\n                    //avoid overwriting existing ones\n                    continue;\n                }\n\n                //copy getters\n                if (origin.prototype.__lookupGetter__(i)) {\n                    target.prototype.__defineGetter__(\n                        i,\n                        origin.prototype.__lookupGetter__(i)\n                    );\n                } else {\n                    target.prototype[i] = origin.prototype[i];\n                }\n\n                //and setters\n                if (origin.prototype.__lookupSetter__(i)) {\n                    target.prototype.__defineSetter__(\n                        i,\n                        origin.prototype.__lookupSetter__(i)\n                    );\n                }\n            }\n        }\n    };\n\n\t//used by some widgets to render a curve editor\n\tfunction CurveEditor( points )\n\t{\n\t\tthis.points = points;\n\t\tthis.selected = -1;\n\t\tthis.nearest = -1;\n\t\tthis.size = null; //stores last size used\n\t\tthis.must_update = true;\n\t\tthis.margin = 5;\n\t}\n\n\tCurveEditor.sampleCurve = function(f,points)\n\t{\n\t\tif(!points)\n\t\t\treturn;\n\t\tfor(var i = 0; i < points.length - 1; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tvar pn = points[i+1];\n\t\t\tif(pn[0] < f)\n\t\t\t\tcontinue;\n\t\t\tvar r = (pn[0] - p[0]);\n\t\t\tif( Math.abs(r) < 0.00001 )\n\t\t\t\treturn p[1];\n\t\t\tvar local_f = (f - p[0]) / r;\n\t\t\treturn p[1] * (1.0 - local_f) + pn[1] * local_f;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tCurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tthis.size = size;\n\t\tvar w = size[0] - this.margin * 2;\n\t\tvar h = size[1] - this.margin * 2;\n\n\t\tline_color = line_color || \"#666\";\n\n\t\tctx.save();\n\t\tctx.translate(this.margin,this.margin);\n\n\t\tif(background_color)\n\t\t{\n\t\t\tctx.fillStyle = \"#111\";\n\t\t\tctx.fillRect(0,0,w,h);\n\t\t\tctx.fillStyle = \"#222\";\n\t\t\tctx.fillRect(w*0.5,0,1,h);\n\t\t\tctx.strokeStyle = \"#333\";\n\t\t\tctx.strokeRect(0,0,w,h);\n\t\t}\n\t\tctx.strokeStyle = line_color;\n\t\tif(inactive)\n\t\t\tctx.globalAlpha = 0.5;\n\t\tctx.beginPath();\n\t\tfor(var i = 0; i < points.length; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tctx.lineTo( p[0] * w, (1.0 - p[1]) * h );\n\t\t}\n\t\tctx.stroke();\n\t\tctx.globalAlpha = 1;\n\t\tif(!inactive)\n\t\t\tfor(var i = 0; i < points.length; ++i)\n\t\t\t{\n\t\t\t\tvar p = points[i];\n\t\t\t\tctx.fillStyle = this.selected == i ? \"#FFF\" : (this.nearest == i ? \"#DDD\" : \"#AAA\");\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 );\n\t\t\t\tctx.fill();\n\t\t\t}\n\t\tctx.restore();\n\t}\n\n\t//localpos is mouse in curve editor space\n\tCurveEditor.prototype.onMouseDown = function( localpos, graphcanvas )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tif( localpos[1] < 0 )\n\t\t\treturn;\n\n\t\t//this.captureInput(true);\n\t\tvar w = this.size[0] - this.margin * 2;\n\t\tvar h = this.size[1] - this.margin * 2;\n\t\tvar x = localpos[0] - this.margin;\n\t\tvar y = localpos[1] - this.margin;\n\t\tvar pos = [x,y];\n\t\tvar max_dist = 30 / graphcanvas.ds.scale;\n\t\t//search closer one\n\t\tthis.selected = this.getCloserPoint(pos, max_dist);\n\t\t//create one\n\t\tif(this.selected == -1)\n\t\t{\n\t\t\tvar point = [x / w, 1 - y / h];\n\t\t\tpoints.push(point);\n\t\t\tpoints.sort(function(a,b){ return a[0] - b[0]; });\n\t\t\tthis.selected = points.indexOf(point);\n\t\t\tthis.must_update = true;\n\t\t}\n\t\tif(this.selected != -1)\n\t\t\treturn true;\n\t}\n\n\tCurveEditor.prototype.onMouseMove = function( localpos, graphcanvas )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tvar s = this.selected;\n\t\tif(s < 0)\n\t\t\treturn;\n\t\tvar x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 );\n\t\tvar y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 );\n\t\tvar curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)];\n\t\tvar max_dist = 30 / graphcanvas.ds.scale;\n\t\tthis._nearest = this.getCloserPoint(curvepos, max_dist);\n\t\tvar point = points[s];\n\t\tif(point)\n\t\t{\n\t\t\tvar is_edge_point = s == 0 || s == points.length - 1;\n\t\t\tif( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) )\n\t\t\t{\n\t\t\t\tpoints.splice(s,1);\n\t\t\t\tthis.selected = -1;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif( !is_edge_point ) //not edges\n\t\t\t\tpoint[0] = clamp(x, 0, 1);\n\t\t\telse\n\t\t\t\tpoint[0] = s == 0 ? 0 : 1;\n\t\t\tpoint[1] = 1.0 - clamp(y, 0, 1);\n\t\t\tpoints.sort(function(a,b){ return a[0] - b[0]; });\n\t\t\tthis.selected = points.indexOf(point);\n\t\t\tthis.must_update = true;\n\t\t}\n\t}\n\n\tCurveEditor.prototype.onMouseUp = function( localpos, graphcanvas )\n\t{\n\t\tthis.selected = -1;\n\t\treturn false;\n\t}\n\n\tCurveEditor.prototype.getCloserPoint = function(pos, max_dist)\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn -1;\n\t\tmax_dist = max_dist || 30;\n\t\tvar w = (this.size[0] - this.margin * 2);\n\t\tvar h = (this.size[1] - this.margin * 2);\n\t\tvar num = points.length;\n\t\tvar p2 = [0,0];\n\t\tvar min_dist = 1000000;\n\t\tvar closest = -1;\n\t\tvar last_valid = -1;\n\t\tfor(var i = 0; i < num; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tp2[0] = p[0] * w;\n\t\t\tp2[1] = (1.0 - p[1]) * h;\n\t\t\tif(p2[0] < pos[0])\n\t\t\t\tlast_valid = i;\n\t\t\tvar dist = vec2.distance(pos,p2);\n\t\t\tif(dist > min_dist || dist > max_dist)\n\t\t\t\tcontinue;\n\t\t\tclosest = i;\n\t\t\tmin_dist = dist;\n\t\t}\n\t\treturn closest;\n\t}\n\n\tLiteGraph.CurveEditor = CurveEditor;\n\n    //used to create nodes from wrapping functions\n    LiteGraph.getParameterNames = function(func) {\n        return (func + \"\")\n            .replace(/[/][/].*$/gm, \"\") // strip single-line comments\n            .replace(/\\s+/g, \"\") // strip white space\n            .replace(/[/][*][^/*]*[*][/]/g, \"\") // strip multi-line comments  /**/\n            .split(\"){\", 1)[0]\n            .replace(/^[^(]*[(]/, \"\") // extract the parameters\n            .replace(/=[^,]+/g, \"\") // strip any ES6 defaults\n            .split(\",\")\n            .filter(Boolean); // split & filter [\"\"]\n    };\n\n\t/* helper for interaction: pointer, touch, mouse Listeners\n\tused by LGraphCanvas DragAndScale ContextMenu*/\n\tLiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) {\n\t\tif (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!==\"function\"){\n\t\t\t//console.log(\"cant pointerListenerAdd \"+oDOM+\", \"+sEvent+\", \"+fCall);\n\t\t\treturn; // -- break --\n\t\t}\n\t\t\n\t\tvar sMethod = LiteGraph.pointerevents_method;\n\t\tvar sEvent = sEvIn;\n\t\t\n\t\t// UNDER CONSTRUCTION\n\t\t// convert pointerevents to touch event when not available\n\t\tif (sMethod==\"pointer\" && !window.PointerEvent){ \n\t\t\tconsole.warn(\"sMethod=='pointer' && !window.PointerEvent\");\n\t\t\tconsole.log(\"Converting pointer[\"+sEvent+\"] : down move up cancel enter TO touchstart touchmove touchend, etc ..\");\n\t\t\tswitch(sEvent){\n\t\t\t\tcase \"down\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\tsEvent = \"start\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"move\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\t//sEvent = \"move\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"up\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\tsEvent = \"end\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"cancel\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\t//sEvent = \"cancel\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"enter\":{\n\t\t\t\t\tconsole.log(\"debug: Should I send a move event?\"); // ???\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// case \"over\": case \"out\": not used at now\n\t\t\t\tdefault:{\n\t\t\t\t\tconsole.warn(\"PointerEvent not available in this browser ? The event \"+sEvent+\" would not be called\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tswitch(sEvent){\n\t\t\t//both pointer and move events\n\t\t\tcase \"down\": case \"up\": case \"move\": case \"over\": case \"out\": case \"enter\":\n\t\t\t{\n\t\t\t\toDOM.addEventListener(sMethod+sEvent, fCall, capture);\n\t\t\t}\n\t\t\t// only pointerevents\n\t\t\tcase \"leave\": case \"cancel\": case \"gotpointercapture\": case \"lostpointercapture\":\n\t\t\t{\n\t\t\t\tif (sMethod!=\"mouse\"){\n\t\t\t\t\treturn oDOM.addEventListener(sMethod+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// not \"pointer\" || \"mouse\"\n\t\t\tdefault:\n\t\t\t\treturn oDOM.addEventListener(sEvent, fCall, capture);\n\t\t}\n\t}\n\tLiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) {\n\t\tif (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!==\"function\"){\n\t\t\t//console.log(\"cant pointerListenerRemove \"+oDOM+\", \"+sEvent+\", \"+fCall);\n\t\t\treturn; // -- break --\n\t\t}\n\t\tswitch(sEvent){\n\t\t\t//both pointer and move events\n\t\t\tcase \"down\": case \"up\": case \"move\": case \"over\": case \"out\": case \"enter\":\n\t\t\t{\n\t\t\t\tif (LiteGraph.pointerevents_method==\"pointer\" || LiteGraph.pointerevents_method==\"mouse\"){\n\t\t\t\t\toDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// only pointerevents\n\t\t\tcase \"leave\": case \"cancel\": case \"gotpointercapture\": case \"lostpointercapture\":\n\t\t\t{\n\t\t\t\tif (LiteGraph.pointerevents_method==\"pointer\"){\n\t\t\t\t\treturn oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// not \"pointer\" || \"mouse\"\n\t\t\tdefault:\n\t\t\t\treturn oDOM.removeEventListener(sEvent, fCall, capture);\n\t\t}\n\t}\n\n    function clamp(v, a, b) {\n        return a > v ? a : b < v ? b : v;\n    };\n    global.clamp = clamp;\n\n    if (typeof window != \"undefined\" && !window[\"requestAnimationFrame\"]) {\n        window.requestAnimationFrame =\n            window.webkitRequestAnimationFrame ||\n            window.mozRequestAnimationFrame ||\n            function(callback) {\n                window.setTimeout(callback, 1000 / 60);\n            };\n    }\n})(this);\n\nif (typeof exports != \"undefined\") {\n    exports.LiteGraph = this.LiteGraph;\n    exports.LGraph = this.LGraph;\n    exports.LLink = this.LLink;\n    exports.LGraphNode = this.LGraphNode;\n    exports.LGraphGroup = this.LGraphGroup;\n    exports.DragAndScale = this.DragAndScale;\n    exports.LGraphCanvas = this.LGraphCanvas;\n    exports.ContextMenu = this.ContextMenu;\n}\n\n\n//basic nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    //Constant\r\n    function Time() {\r\n        this.addOutput(\"in ms\", \"number\");\r\n        this.addOutput(\"in sec\", \"number\");\r\n    }\r\n\r\n    Time.title = \"Time\";\r\n    Time.desc = \"Time\";\r\n\r\n    Time.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.graph.globaltime * 1000);\r\n        this.setOutputData(1, this.graph.globaltime);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/time\", Time);\r\n\r\n    //Subgraph: a node that contains a graph\r\n    function Subgraph() {\r\n        var that = this;\r\n        this.size = [140, 80];\r\n        this.properties = { enabled: true };\r\n        this.enabled = true;\r\n\r\n        //create inner graph\r\n        this.subgraph = new LiteGraph.LGraph();\r\n        this.subgraph._subgraph_node = this;\r\n        this.subgraph._is_subgraph = true;\r\n\r\n        this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this);\r\n\r\n\t\t//nodes input node added inside\r\n        this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this);\r\n        this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this);\r\n        this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(this);\r\n        this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this);\r\n\r\n        this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this);\r\n        this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this);\r\n        this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(this);\r\n        this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this);\r\n    }\r\n\r\n    Subgraph.title = \"Subgraph\";\r\n    Subgraph.desc = \"Graph inside a node\";\r\n    Subgraph.title_color = \"#334\";\r\n\r\n    Subgraph.prototype.onGetInputs = function() {\r\n        return [[\"enabled\", \"boolean\"]];\r\n    };\r\n\r\n\t/*\r\n    Subgraph.prototype.onDrawTitle = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        ctx.fillStyle = \"#555\";\r\n        var w = LiteGraph.NODE_TITLE_HEIGHT;\r\n        var x = this.size[0] - w;\r\n        ctx.fillRect(x, -w, w, w);\r\n        ctx.fillStyle = \"#333\";\r\n        ctx.beginPath();\r\n        ctx.moveTo(x + w * 0.2, -w * 0.6);\r\n        ctx.lineTo(x + w * 0.8, -w * 0.6);\r\n        ctx.lineTo(x + w * 0.5, -w * 0.3);\r\n        ctx.fill();\r\n    };\r\n\t*/\r\n\r\n    Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) {\r\n        var that = this;\r\n        setTimeout(function() {\r\n            graphcanvas.openSubgraph(that.subgraph);\r\n        }, 10);\r\n    };\r\n\r\n\t/*\r\n    Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) {\r\n        if (\r\n            !this.flags.collapsed &&\r\n            pos[0] > this.size[0] - LiteGraph.NODE_TITLE_HEIGHT &&\r\n            pos[1] < 0\r\n        ) {\r\n            var that = this;\r\n            setTimeout(function() {\r\n                graphcanvas.openSubgraph(that.subgraph);\r\n            }, 10);\r\n        }\r\n    };\r\n\t*/\r\n\r\n    Subgraph.prototype.onAction = function(action, param) {\r\n        this.subgraph.onAction(action, param);\r\n    };\r\n\r\n    Subgraph.prototype.onExecute = function() {\r\n        this.enabled = this.getInputOrProperty(\"enabled\");\r\n        if (!this.enabled) {\r\n            return;\r\n        }\r\n\r\n        //send inputs to subgraph global inputs\r\n        if (this.inputs) {\r\n            for (var i = 0; i < this.inputs.length; i++) {\r\n                var input = this.inputs[i];\r\n                var value = this.getInputData(i);\r\n                this.subgraph.setInputData(input.name, value);\r\n            }\r\n        }\r\n\r\n        //execute\r\n        this.subgraph.runStep();\r\n\r\n        //send subgraph global outputs to outputs\r\n        if (this.outputs) {\r\n            for (var i = 0; i < this.outputs.length; i++) {\r\n                var output = this.outputs[i];\r\n                var value = this.subgraph.getOutputData(output.name);\r\n                this.setOutputData(i, value);\r\n            }\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.sendEventToAllNodes = function(eventname, param, mode) {\r\n        if (this.enabled) {\r\n            this.subgraph.sendEventToAllNodes(eventname, param, mode);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onDrawBackground = function (ctx, graphcanvas, canvas, pos) {\r\n        if (this.flags.collapsed)\r\n            return;\r\n        var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n        // button\r\n        var over = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0], LiteGraph.NODE_TITLE_HEIGHT);\r\n        let overleft = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0] / 2, LiteGraph.NODE_TITLE_HEIGHT)\r\n        ctx.fillStyle = over ? \"#555\" : \"#222\";\r\n        ctx.beginPath();\r\n        if (this._shape == LiteGraph.BOX_SHAPE) {\r\n            if (overleft) {\r\n                ctx.rect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT);\r\n            } else {\r\n                ctx.rect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT);\r\n            }\r\n        }\r\n        else {\r\n            if (overleft) {\r\n                ctx.roundRect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]);\r\n            } else {\r\n                ctx.roundRect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]);\r\n            }\r\n        }\r\n        if (over) {\r\n            ctx.fill();\r\n        } else {\r\n            ctx.fillRect(0, y, this.size[0] + 1, LiteGraph.NODE_TITLE_HEIGHT);\r\n        }\r\n        // button\r\n        ctx.textAlign = \"center\";\r\n        ctx.font = \"24px Arial\";\r\n        ctx.fillStyle = over ? \"#DDD\" : \"#999\";\r\n        ctx.fillText(\"+\", this.size[0] * 0.25, y + 24);\r\n        ctx.fillText(\"+\", this.size[0] * 0.75, y + 24);\r\n    }\r\n\r\n    // Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas)\r\n    // {\r\n    // \tvar y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n    // \tif(localpos[1] > y)\r\n    // \t{\r\n    // \t\tgraphcanvas.showSubgraphPropertiesDialog(this);\r\n    // \t}\r\n    // }\r\n    Subgraph.prototype.onMouseDown = function (e, localpos, graphcanvas) {\r\n        var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n        console.log(0)\r\n        if (localpos[1] > y) {\r\n            if (localpos[0] < this.size[0] / 2) {\r\n                console.log(1)\r\n                graphcanvas.showSubgraphPropertiesDialog(this);\r\n            } else {\r\n                console.log(2)\r\n                graphcanvas.showSubgraphPropertiesDialogRight(this);\r\n            }\r\n        }\r\n    }\r\n\tSubgraph.prototype.computeSize = function()\r\n\t{\r\n\t\tvar num_inputs = this.inputs ? this.inputs.length : 0;\r\n\t\tvar num_outputs = this.outputs ? this.outputs.length : 0;\r\n\t\treturn [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ];\r\n\t}\r\n\r\n    //**** INPUTS ***********************************\r\n    Subgraph.prototype.onSubgraphTrigger = function(event, param) {\r\n        var slot = this.findOutputSlot(event);\r\n        if (slot != -1) {\r\n            this.triggerSlot(slot);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphNewInput = function(name, type) {\r\n        var slot = this.findInputSlot(name);\r\n        if (slot == -1) {\r\n            //add input to the node\r\n            this.addInput(name, type);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRenamedInput = function(oldname, name) {\r\n        var slot = this.findInputSlot(oldname);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getInputInfo(slot);\r\n        info.name = name;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphTypeChangeInput = function(name, type) {\r\n        var slot = this.findInputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getInputInfo(slot);\r\n        info.type = type;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRemovedInput = function(name) {\r\n        var slot = this.findInputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        this.removeInput(slot);\r\n    };\r\n\r\n    //**** OUTPUTS ***********************************\r\n    Subgraph.prototype.onSubgraphNewOutput = function(name, type) {\r\n        var slot = this.findOutputSlot(name);\r\n        if (slot == -1) {\r\n            this.addOutput(name, type);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRenamedOutput = function(oldname, name) {\r\n        var slot = this.findOutputSlot(oldname);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getOutputInfo(slot);\r\n        info.name = name;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphTypeChangeOutput = function(name, type) {\r\n        var slot = this.findOutputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getOutputInfo(slot);\r\n        info.type = type;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRemovedOutput = function(name) {\r\n        var slot = this.findOutputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        this.removeOutput(slot);\r\n    };\r\n    // *****************************************************\r\n\r\n    Subgraph.prototype.getExtraMenuOptions = function(graphcanvas) {\r\n        var that = this;\r\n        return [\r\n            {\r\n                content: \"Open\",\r\n                callback: function() {\r\n                    graphcanvas.openSubgraph(that.subgraph);\r\n                }\r\n            }\r\n        ];\r\n    };\r\n\r\n    Subgraph.prototype.onResize = function(size) {\r\n        size[1] += 20;\r\n    };\r\n\r\n    Subgraph.prototype.serialize = function() {\r\n        var data = LiteGraph.LGraphNode.prototype.serialize.call(this);\r\n        data.subgraph = this.subgraph.serialize();\r\n        return data;\r\n    };\r\n    //no need to define node.configure, the default method detects node.subgraph and passes the object to node.subgraph.configure()\r\n\r\n    Subgraph.prototype.reassignSubgraphUUIDs = function(graph) {\r\n        const idMap = { nodeIDs: {}, linkIDs: {} }\r\n\r\n        for (const node of graph.nodes) {\r\n            const oldID = node.id\r\n            const newID = LiteGraph.uuidv4()\r\n            node.id = newID\r\n\r\n            if (idMap.nodeIDs[oldID] || idMap.nodeIDs[newID]) {\r\n                throw new Error(`New/old node UUID wasn't unique in changed map! ${oldID} ${newID}`)\r\n            }\r\n\r\n            idMap.nodeIDs[oldID] = newID\r\n            idMap.nodeIDs[newID] = oldID\r\n        }\r\n\r\n        for (const link of graph.links) {\r\n            const oldID = link[0]\r\n            const newID = LiteGraph.uuidv4();\r\n            link[0] = newID\r\n\r\n            if (idMap.linkIDs[oldID] || idMap.linkIDs[newID]) {\r\n                throw new Error(`New/old link UUID wasn't unique in changed map! ${oldID} ${newID}`)\r\n            }\r\n\r\n            idMap.linkIDs[oldID] = newID\r\n            idMap.linkIDs[newID] = oldID\r\n\r\n            const nodeFrom = link[1]\r\n            const nodeTo = link[3]\r\n\r\n            if (!idMap.nodeIDs[nodeFrom]) {\r\n                throw new Error(`Old node UUID not found in mapping! ${nodeFrom}`)\r\n            }\r\n\r\n            link[1] = idMap.nodeIDs[nodeFrom]\r\n\r\n            if (!idMap.nodeIDs[nodeTo]) {\r\n                throw new Error(`Old node UUID not found in mapping! ${nodeTo}`)\r\n            }\r\n\r\n            link[3] = idMap.nodeIDs[nodeTo]\r\n        }\r\n\r\n        // Reconnect links\r\n        for (const node of graph.nodes) {\r\n            if (node.inputs) {\r\n                for (const input of node.inputs) {\r\n                    if (input.link) {\r\n                        input.link = idMap.linkIDs[input.link]\r\n                    }\r\n                }\r\n            }\r\n            if (node.outputs) {\r\n                for (const output of node.outputs) {\r\n                    if (output.links) {\r\n                        output.links = output.links.map(l => idMap.linkIDs[l]);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        // Recurse!\r\n        for (const node of graph.nodes) {\r\n            if (node.type === \"graph/subgraph\") {\r\n                const merge = reassignGraphUUIDs(node.subgraph);\r\n                idMap.nodeIDs.assign(merge.nodeIDs)\r\n                idMap.linkIDs.assign(merge.linkIDs)\r\n            }\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.clone = function() {\r\n        var node = LiteGraph.createNode(this.type);\r\n        var data = this.serialize();\r\n\r\n        if (LiteGraph.use_uuids) {\r\n            // LGraph.serialize() seems to reuse objects in the original graph. But we\r\n            // need to change node IDs here, so clone it first.\r\n            const subgraph = LiteGraph.cloneObject(data.subgraph)\r\n\r\n            this.reassignSubgraphUUIDs(subgraph);\r\n\r\n            data.subgraph = subgraph;\r\n        }\r\n\r\n        delete data[\"id\"];\r\n        delete data[\"inputs\"];\r\n        delete data[\"outputs\"];\r\n        node.configure(data);\r\n        return node;\r\n    };\r\n\r\n\tSubgraph.prototype.buildFromNodes = function(nodes)\r\n\t{\r\n\t\t//clear all?\r\n\t\t//TODO\r\n\r\n\t\t//nodes that connect data between parent graph and subgraph\r\n\t\tvar subgraph_inputs = [];\r\n\t\tvar subgraph_outputs = [];\r\n\r\n\t\t//mark inner nodes\r\n\t\tvar ids = {};\r\n\t\tvar min_x = 0;\r\n\t\tvar max_x = 0;\r\n\t\tfor(var i = 0; i < nodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar node = nodes[i];\r\n\t\t\tids[ node.id ] = node;\r\n\t\t\tmin_x = Math.min( node.pos[0], min_x );\r\n\t\t\tmax_x = Math.max( node.pos[0], min_x );\r\n\t\t}\r\n\t\t\r\n\t\tvar last_input_y = 0;\r\n\t\tvar last_output_y = 0;\r\n\r\n\t\tfor(var i = 0; i < nodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar node = nodes[i];\r\n\t\t\t//check inputs\r\n\t\t\tif( node.inputs )\r\n\t\t\t\tfor(var j = 0; j < node.inputs.length; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar input = node.inputs[j];\r\n\t\t\t\t\tif( !input || !input.link )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tvar link = node.graph.links[ input.link ];\r\n\t\t\t\t\tif(!link)\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tif( ids[ link.origin_id ] )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t//this.addInput(input.name,link.type);\r\n\t\t\t\t\tthis.subgraph.addInput(input.name,link.type);\r\n\t\t\t\t\t/*\r\n\t\t\t\t\tvar input_node = LiteGraph.createNode(\"graph/input\");\r\n\t\t\t\t\tthis.subgraph.add( input_node );\r\n\t\t\t\t\tinput_node.pos = [min_x - 200, last_input_y ];\r\n\t\t\t\t\tlast_input_y += 100;\r\n\t\t\t\t\t*/\r\n\t\t\t\t}\r\n\r\n\t\t\t//check outputs\r\n\t\t\tif( node.outputs )\r\n\t\t\t\tfor(var j = 0; j < node.outputs.length; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar output = node.outputs[j];\r\n\t\t\t\t\tif( !output || !output.links || !output.links.length )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tvar is_external = false;\r\n\t\t\t\t\tfor(var k = 0; k < output.links.length; ++k)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar link = node.graph.links[ output.links[k] ];\r\n\t\t\t\t\t\tif(!link)\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\tif( ids[ link.target_id ] )\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\tis_external = true;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif(!is_external)\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t//this.addOutput(output.name,output.type);\r\n\t\t\t\t\t/*\r\n\t\t\t\t\tvar output_node = LiteGraph.createNode(\"graph/output\");\r\n\t\t\t\t\tthis.subgraph.add( output_node );\r\n\t\t\t\t\toutput_node.pos = [max_x + 50, last_output_y ];\r\n\t\t\t\t\tlast_output_y += 100;\r\n\t\t\t\t\t*/\r\n\t\t\t\t}\r\n\t\t}\r\n\r\n\t\t//detect inputs and outputs\r\n\t\t\t//split every connection in two data_connection nodes\r\n\t\t\t//keep track of internal connections\r\n\t\t\t//connect external connections\r\n\r\n\t\t//clone nodes inside subgraph and try to reconnect them\r\n\r\n\t\t//connect edge subgraph nodes to extarnal connections nodes\r\n\t}\r\n\r\n    LiteGraph.Subgraph = Subgraph;\r\n    LiteGraph.registerNodeType(\"graph/subgraph\", Subgraph);\r\n\r\n    //Input for a subgraph\r\n    function GraphInput() {\r\n        this.addOutput(\"\", \"number\");\r\n\r\n        this.name_in_graph = \"\";\r\n        this.properties = {\r\n\t\t\tname: \"\",\r\n\t\t\ttype: \"number\",\r\n\t\t\tvalue: 0\r\n\t\t}; \r\n\r\n        var that = this;\r\n\r\n        this.name_widget = this.addWidget(\r\n            \"text\",\r\n            \"Name\",\r\n            this.properties.name,\r\n            function(v) {\r\n                if (!v) {\r\n                    return;\r\n                }\r\n                that.setProperty(\"name\",v);\r\n            }\r\n        );\r\n        this.type_widget = this.addWidget(\r\n            \"text\",\r\n            \"Type\",\r\n            this.properties.type,\r\n            function(v) {\r\n\t\t\t\tthat.setProperty(\"type\",v);\r\n            }\r\n        );\r\n\r\n        this.value_widget = this.addWidget(\r\n            \"number\",\r\n            \"Value\",\r\n            this.properties.value,\r\n            function(v) {\r\n                that.setProperty(\"value\",v);\r\n            }\r\n        );\r\n\r\n        this.widgets_up = true;\r\n        this.size = [180, 90];\r\n    }\r\n\r\n    GraphInput.title = \"Input\";\r\n    GraphInput.desc = \"Input of the graph\";\r\n\r\n\tGraphInput.prototype.onConfigure = function()\r\n\r\n\t{\r\n\t\tthis.updateType();\r\n\t}\r\n\r\n\t//ensures the type in the node output and the type in the associated graph input are the same\r\n\tGraphInput.prototype.updateType = function()\r\n\t{\r\n\t\tvar type = this.properties.type;\r\n\t\tthis.type_widget.value = type;\r\n\r\n\t\t//update output\r\n\t\tif(this.outputs[0].type != type)\r\n\t\t{\r\n\t        if (!LiteGraph.isValidConnection(this.outputs[0].type,type))\r\n\t\t\t\tthis.disconnectOutput(0);\r\n\t\t\tthis.outputs[0].type = type;\r\n\t\t}\r\n\r\n\t\t//update widget\r\n\t\tif(type == \"number\")\r\n\t\t{\r\n\t\t\tthis.value_widget.type = \"number\";\r\n\t\t\tthis.value_widget.value = 0;\r\n\t\t}\r\n\t\telse if(type == \"boolean\")\r\n\t\t{\r\n\t\t\tthis.value_widget.type = \"toggle\";\r\n\t\t\tthis.value_widget.value = true;\r\n\t\t}\r\n\t\telse if(type == \"string\")\r\n\t\t{\r\n\t\t\tthis.value_widget.type = \"text\";\r\n\t\t\tthis.value_widget.value = \"\";\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.value_widget.type = null;\r\n\t\t\tthis.value_widget.value = null;\r\n\t\t}\r\n\t\tthis.properties.value = this.value_widget.value;\r\n\r\n\t\t//update graph\r\n\t\tif (this.graph && this.name_in_graph) {\r\n\t\t\tthis.graph.changeInputType(this.name_in_graph, type);\r\n\t\t}\r\n\t}\r\n\r\n\t//this is executed AFTER the property has changed\r\n\tGraphInput.prototype.onPropertyChanged = function(name,v)\r\n\t{\r\n\t\tif( name == \"name\" )\r\n\t\t{\r\n\t\t\tif (v == \"\" || v == this.name_in_graph || v == \"enabled\") {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tif(this.graph)\r\n\t\t\t{\r\n\t\t\t\tif (this.name_in_graph) {\r\n\t\t\t\t\t//already added\r\n\t\t\t\t\tthis.graph.renameInput( this.name_in_graph, v );\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.graph.addInput( v, this.properties.type );\r\n\t\t\t\t}\r\n\t\t\t} //what if not?!\r\n\t\t\tthis.name_widget.value = v;\r\n\t\t\tthis.name_in_graph = v;\r\n\t\t}\r\n\t\telse if( name == \"type\" )\r\n\t\t{\r\n\t\t\tthis.updateType();\r\n\t\t}\r\n\t\telse if( name == \"value\" )\r\n\t\t{\r\n\t\t}\r\n\t}\r\n\r\n    GraphInput.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.name;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    GraphInput.prototype.onAction = function(action, param) {\r\n        if (this.properties.type == LiteGraph.EVENT) {\r\n            this.triggerSlot(0, param);\r\n        }\r\n    };\r\n\r\n    GraphInput.prototype.onExecute = function() {\r\n        var name = this.properties.name;\r\n        //read from global input\r\n        var data = this.graph.inputs[name];\r\n        if (!data) {\r\n            this.setOutputData(0, this.properties.value );\r\n\t\t\treturn;\r\n        }\r\n\r\n        this.setOutputData(0, data.value !== undefined ? data.value : this.properties.value );\r\n    };\r\n\r\n    GraphInput.prototype.onRemoved = function() {\r\n        if (this.name_in_graph) {\r\n            this.graph.removeInput(this.name_in_graph);\r\n        }\r\n    };\r\n\r\n    LiteGraph.GraphInput = GraphInput;\r\n    LiteGraph.registerNodeType(\"graph/input\", GraphInput);\r\n\r\n    //Output for a subgraph\r\n    function GraphOutput() {\r\n        this.addInput(\"\", \"\");\r\n\r\n        this.name_in_graph = \"\";\r\n        this.properties = { name: \"\", type: \"\" };\r\n        var that = this;\r\n\r\n        // Object.defineProperty(this.properties, \"name\", {\r\n        //     get: function() {\r\n        //         return that.name_in_graph;\r\n        //     },\r\n        //     set: function(v) {\r\n        //         if (v == \"\" || v == that.name_in_graph) {\r\n        //             return;\r\n        //         }\r\n        //         if (that.name_in_graph) {\r\n        //             //already added\r\n        //             that.graph.renameOutput(that.name_in_graph, v);\r\n        //         } else {\r\n        //             that.graph.addOutput(v, that.properties.type);\r\n        //         }\r\n        //         that.name_widget.value = v;\r\n        //         that.name_in_graph = v;\r\n        //     },\r\n        //     enumerable: true\r\n        // });\r\n\r\n        // Object.defineProperty(this.properties, \"type\", {\r\n        //     get: function() {\r\n        //         return that.inputs[0].type;\r\n        //     },\r\n        //     set: function(v) {\r\n        //         if (v == \"action\" || v == \"event\") {\r\n        //             v = LiteGraph.ACTION;\r\n        //         }\r\n\t\t//         if (!LiteGraph.isValidConnection(that.inputs[0].type,v))\r\n\t\t// \t\t\tthat.disconnectInput(0);\r\n        //         that.inputs[0].type = v;\r\n        //         if (that.name_in_graph) {\r\n        //             //already added\r\n        //             that.graph.changeOutputType(\r\n        //                 that.name_in_graph,\r\n        //                 that.inputs[0].type\r\n        //             );\r\n        //         }\r\n        //         that.type_widget.value = v || \"\";\r\n        //     },\r\n        //     enumerable: true\r\n        // });\r\n\r\n        this.name_widget = this.addWidget(\"text\",\"Name\",this.properties.name,\"name\");\r\n        this.type_widget = this.addWidget(\"text\",\"Type\",this.properties.type,\"type\");\r\n        this.widgets_up = true;\r\n        this.size = [180, 60];\r\n    }\r\n\r\n    GraphOutput.title = \"Output\";\r\n    GraphOutput.desc = \"Output of the graph\";\r\n\r\n    GraphOutput.prototype.onPropertyChanged = function (name, v) {\r\n        if (name == \"name\") {\r\n            if (v == \"\" || v == this.name_in_graph || v == \"enabled\") {\r\n                return false;\r\n            }\r\n            if (this.graph) {\r\n                if (this.name_in_graph) {\r\n                    //already added\r\n                    this.graph.renameOutput(this.name_in_graph, v);\r\n                } else {\r\n                    this.graph.addOutput(v, this.properties.type);\r\n                }\r\n            } //what if not?!\r\n            this.name_widget.value = v;\r\n            this.name_in_graph = v;\r\n        }\r\n        else if (name == \"type\") {\r\n            this.updateType();\r\n        }\r\n        else if (name == \"value\") {\r\n        }\r\n    }\r\n     \r\n    GraphOutput.prototype.updateType = function () {\r\n        var type = this.properties.type;\r\n        if (this.type_widget)\r\n            this.type_widget.value = type;\r\n\r\n        //update output\r\n        if (this.inputs[0].type != type) {\r\n\r\n\t\t\tif ( type == \"action\" || type == \"event\")\r\n\t            type = LiteGraph.EVENT;\r\n\t\t\tif (!LiteGraph.isValidConnection(this.inputs[0].type, type))\r\n\t\t\t\tthis.disconnectInput(0);\r\n\t\t\tthis.inputs[0].type = type;\r\n        }\r\n\r\n        //update graph\r\n        if (this.graph && this.name_in_graph) {\r\n            this.graph.changeOutputType(this.name_in_graph, type);\r\n        }\r\n    }\r\n\r\n\r\n\r\n    GraphOutput.prototype.onExecute = function() {\r\n        this._value = this.getInputData(0);\r\n        this.graph.setOutputData(this.properties.name, this._value);\r\n    };\r\n\r\n    GraphOutput.prototype.onAction = function(action, param) {\r\n        if (this.properties.type == LiteGraph.ACTION) {\r\n            this.graph.trigger( this.properties.name, param );\r\n        }\r\n    };\r\n\r\n    GraphOutput.prototype.onRemoved = function() {\r\n        if (this.name_in_graph) {\r\n            this.graph.removeOutput(this.name_in_graph);\r\n        }\r\n    };\r\n\r\n    GraphOutput.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.name;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    LiteGraph.GraphOutput = GraphOutput;\r\n    LiteGraph.registerNodeType(\"graph/output\", GraphOutput);\r\n\r\n    //Constant\r\n    function ConstantNumber() {\r\n        this.addOutput(\"value\", \"number\");\r\n        this.addProperty(\"value\", 1.0);\r\n        this.widget = this.addWidget(\"number\",\"value\",1,\"value\");\r\n        this.widgets_up = true;\r\n        this.size = [180, 30];\r\n    }\r\n\r\n    ConstantNumber.title = \"Const Number\";\r\n    ConstantNumber.desc = \"Constant number\";\r\n\r\n    ConstantNumber.prototype.onExecute = function() {\r\n        this.setOutputData(0, parseFloat(this.properties[\"value\"]));\r\n    };\r\n\r\n    ConstantNumber.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.value;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n\tConstantNumber.prototype.setValue = function(v)\r\n\t{\r\n\t\tthis.setProperty(\"value\",v);\r\n\t}\r\n\r\n    ConstantNumber.prototype.onDrawBackground = function(ctx) {\r\n        //show the current value\r\n        this.outputs[0].label = this.properties[\"value\"].toFixed(3);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/const\", ConstantNumber);\r\n\r\n    function ConstantBoolean() {\r\n        this.addOutput(\"bool\", \"boolean\");\r\n        this.addProperty(\"value\", true);\r\n        this.widget = this.addWidget(\"toggle\",\"value\",true,\"value\");\r\n        this.serialize_widgets = true;\r\n        this.widgets_up = true;\r\n        this.size = [140, 30];\r\n    }\r\n\r\n    ConstantBoolean.title = \"Const Boolean\";\r\n    ConstantBoolean.desc = \"Constant boolean\";\r\n    ConstantBoolean.prototype.getTitle = ConstantNumber.prototype.getTitle;\r\n\r\n    ConstantBoolean.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.properties[\"value\"]);\r\n    };\r\n\r\n\tConstantBoolean.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n\tConstantBoolean.prototype.onGetInputs = function() {\r\n\t\treturn [[\"toggle\", LiteGraph.ACTION]];\r\n\t};\r\n\r\n\tConstantBoolean.prototype.onAction = function(action)\r\n\t{\r\n\t\tthis.setValue( !this.properties.value );\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/boolean\", ConstantBoolean);\r\n\r\n    function ConstantString() {\r\n        this.addOutput(\"string\", \"string\");\r\n        this.addProperty(\"value\", \"\");\r\n        this.widget = this.addWidget(\"text\",\"value\",\"\",\"value\");  //link to property value\r\n        this.widgets_up = true;\r\n        this.size = [180, 30];\r\n    }\r\n\r\n    ConstantString.title = \"Const String\";\r\n    ConstantString.desc = \"Constant string\";\r\n\r\n    ConstantString.prototype.getTitle = ConstantNumber.prototype.getTitle;\r\n\r\n    ConstantString.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.properties[\"value\"]);\r\n    };\r\n\r\n\tConstantString.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n\tConstantString.prototype.onDropFile = function(file)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tvar reader = new FileReader();\r\n\t\treader.onload = function(e)\r\n\t\t{\r\n\t\t\tthat.setProperty(\"value\",e.target.result);\r\n\t\t}\r\n\t\treader.readAsText(file);\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/string\", ConstantString);\r\n\r\n    function ConstantObject() {\r\n        this.addOutput(\"obj\", \"object\");\r\n        this.size = [120, 30];\r\n\t\tthis._object = {};\r\n    }\r\n\r\n    ConstantObject.title = \"Const Object\";\r\n    ConstantObject.desc = \"Constant Object\";\r\n\r\n    ConstantObject.prototype.onExecute = function() {\r\n        this.setOutputData(0, this._object);\r\n    };\r\n\r\n    LiteGraph.registerNodeType( \"basic/object\", ConstantObject );\r\n\r\n    function ConstantFile() {\r\n        this.addInput(\"url\", \"string\");\r\n        this.addOutput(\"file\", \"string\");\r\n        this.addProperty(\"url\", \"\");\r\n        this.addProperty(\"type\", \"text\");\r\n        this.widget = this.addWidget(\"text\",\"url\",\"\",\"url\");\r\n        this._data = null;\r\n    }\r\n\r\n    ConstantFile.title = \"Const File\";\r\n    ConstantFile.desc = \"Fetches a file from an url\";\r\n    ConstantFile[\"@type\"] = { type: \"enum\", values: [\"text\",\"arraybuffer\",\"blob\",\"json\"] };\r\n\r\n    ConstantFile.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"url\")\r\n\t\t{\r\n\t\t\tif( value == null || value == \"\")\r\n\t\t\t\tthis._data = null;\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.fetchFile(value);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n    ConstantFile.prototype.onExecute = function() {\r\n\t\tvar url = this.getInputData(0) || this.properties.url;\r\n\t\tif(url && (url != this._url || this._type != this.properties.type))\r\n\t\t\tthis.fetchFile(url);\r\n        this.setOutputData(0, this._data );\r\n    };\r\n\r\n\tConstantFile.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n    ConstantFile.prototype.fetchFile = function(url) {\r\n\t\tvar that = this;\r\n\t\tif(!url || url.constructor !== String)\r\n\t\t{\r\n\t\t\tthat._data = null;\r\n            that.boxcolor = null;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis._url = url;\r\n\t\tthis._type = this.properties.type;\r\n        if (url.substr(0, 4) == \"http\" && LiteGraph.proxy) {\r\n            url = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\r\n        }\r\n\t\tfetch(url)\r\n\t\t.then(function(response) {\r\n\t\t\tif(!response.ok)\r\n\t\t\t\t throw new Error(\"File not found\");\r\n\r\n\t\t\tif(that.properties.type == \"arraybuffer\")\r\n\t\t\t\treturn response.arrayBuffer();\r\n\t\t\telse if(that.properties.type == \"text\")\r\n\t\t\t\treturn response.text();\r\n\t\t\telse if(that.properties.type == \"json\")\r\n\t\t\t\treturn response.json();\r\n\t\t\telse if(that.properties.type == \"blob\")\r\n\t\t\t\treturn response.blob();\r\n\t\t})\r\n\t\t.then(function(data) {\r\n\t\t\tthat._data = data;\r\n            that.boxcolor = \"#AEA\";\r\n\t\t})\r\n\t\t.catch(function(error) {\r\n\t\t\tthat._data = null;\r\n            that.boxcolor = \"red\";\r\n\t\t\tconsole.error(\"error fetching file:\",url);\r\n\t\t});\r\n    };\r\n\r\n\tConstantFile.prototype.onDropFile = function(file)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tthis._url = file.name;\r\n\t\tthis._type = this.properties.type;\r\n\t\tthis.properties.url = file.name;\r\n\t\tvar reader = new FileReader();\r\n\t\treader.onload = function(e)\r\n\t\t{\r\n            that.boxcolor = \"#AEA\";\r\n\t\t\tvar v = e.target.result;\r\n\t\t\tif( that.properties.type == \"json\" )\r\n\t\t\t\tv = JSON.parse(v);\r\n\t\t\tthat._data = v;\r\n\t\t}\r\n\t\tif(that.properties.type == \"arraybuffer\")\r\n\t\t\treader.readAsArrayBuffer(file);\r\n\t\telse if(that.properties.type == \"text\" || that.properties.type == \"json\")\r\n\t\t\treader.readAsText(file);\r\n\t\telse if(that.properties.type == \"blob\")\r\n\t\t\treturn reader.readAsBinaryString(file);\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/file\", ConstantFile);\r\n\r\n\r\n//to store json objects\r\nfunction JSONParse() {\r\n\tthis.addInput(\"parse\", LiteGraph.ACTION);\r\n\tthis.addInput(\"json\", \"string\");\r\n\tthis.addOutput(\"done\", LiteGraph.EVENT);\r\n\tthis.addOutput(\"object\", \"object\");\r\n\tthis.widget = this.addWidget(\"button\",\"parse\",\"\",this.parse.bind(this));\r\n\tthis._str = null;\r\n\tthis._obj = null;\r\n}\r\n\r\nJSONParse.title = \"JSON Parse\";\r\nJSONParse.desc = \"Parses JSON String into object\";\r\n\r\nJSONParse.prototype.parse = function()\r\n{\r\n\tif(!this._str)\r\n\t\treturn;\r\n\r\n\ttry {\r\n\t\tthis._str = this.getInputData(1);\r\n\t\tthis._obj = JSON.parse(this._str);\r\n\t\tthis.boxcolor = \"#AEA\";\r\n\t\tthis.triggerSlot(0);\r\n\t} catch (err) {\r\n\t\tthis.boxcolor = \"red\";\r\n\t}\r\n}\r\n\r\nJSONParse.prototype.onExecute = function() {\r\n\tthis._str = this.getInputData(1);\r\n\tthis.setOutputData(1, this._obj);\r\n};\r\n\r\nJSONParse.prototype.onAction = function(name) {\r\n\tif(name == \"parse\")\r\n\t\tthis.parse();\r\n}\r\n\r\nLiteGraph.registerNodeType(\"basic/jsonparse\", JSONParse);\t\r\n\r\n\t//to store json objects\r\n    function ConstantData() {\r\n        this.addOutput(\"data\", \"object\");\r\n        this.addProperty(\"value\", \"\");\r\n        this.widget = this.addWidget(\"text\",\"json\",\"\",\"value\");\r\n        this.widgets_up = true;\r\n        this.size = [140, 30];\r\n        this._value = null;\r\n    }\r\n\r\n    ConstantData.title = \"Const Data\";\r\n    ConstantData.desc = \"Constant Data\";\r\n\r\n    ConstantData.prototype.onPropertyChanged = function(name, value) {\r\n        this.widget.value = value;\r\n        if (value == null || value == \"\") {\r\n            return;\r\n        }\r\n\r\n        try {\r\n            this._value = JSON.parse(value);\r\n            this.boxcolor = \"#AEA\";\r\n        } catch (err) {\r\n            this.boxcolor = \"red\";\r\n        }\r\n    };\r\n\r\n    ConstantData.prototype.onExecute = function() {\r\n        this.setOutputData(0, this._value);\r\n    };\r\n\r\n\tConstantData.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n    LiteGraph.registerNodeType(\"basic/data\", ConstantData);\r\n\r\n\t//to store json objects\r\n    function ConstantArray() {\r\n\t\tthis._value = [];\r\n        this.addInput(\"json\", \"\");\r\n        this.addOutput(\"arrayOut\", \"array\");\r\n\t\tthis.addOutput(\"length\", \"number\");\r\n        this.addProperty(\"value\", \"[]\");\r\n        this.widget = this.addWidget(\"text\",\"array\",this.properties.value,\"value\");\r\n        this.widgets_up = true;\r\n        this.size = [140, 50];\r\n    }\r\n\r\n    ConstantArray.title = \"Const Array\";\r\n    ConstantArray.desc = \"Constant Array\";\r\n\r\n    ConstantArray.prototype.onPropertyChanged = function(name, value) {\r\n        this.widget.value = value;\r\n        if (value == null || value == \"\") {\r\n            return;\r\n        }\r\n\r\n        try {\r\n\t\t\tif(value[0] != \"[\")\r\n\t            this._value = JSON.parse(\"[\" + value + \"]\");\r\n\t\t\telse\r\n\t            this._value = JSON.parse(value);\r\n            this.boxcolor = \"#AEA\";\r\n        } catch (err) {\r\n            this.boxcolor = \"red\";\r\n        }\r\n    };\r\n\r\n    ConstantArray.prototype.onExecute = function() {\r\n        var v = this.getInputData(0);\r\n\t\tif(v && v.length) //clone\r\n\t\t{\r\n\t\t\tif(!this._value)\r\n\t\t\t\tthis._value = new Array();\r\n\t\t\tthis._value.length = v.length;\r\n\t\t\tfor(var i = 0; i < v.length; ++i)\r\n\t\t\t\tthis._value[i] = v[i];\r\n\t\t}\r\n\t\tthis.setOutputData(0, this._value);\r\n\t\tthis.setOutputData(1, this._value ? ( this._value.length || 0) : 0 );\r\n    };\r\n\r\n\tConstantArray.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n    LiteGraph.registerNodeType(\"basic/array\", ConstantArray);\r\n\r\n\tfunction SetArray()\r\n\t{\r\n        this.addInput(\"arr\", \"array\");\r\n        this.addInput(\"value\", \"\");\r\n        this.addOutput(\"arr\", \"array\");\r\n\t\tthis.properties = { index: 0 };\r\n        this.widget = this.addWidget(\"number\",\"i\",this.properties.index,\"index\",{precision: 0, step: 10, min: 0});\r\n\t}\r\n\r\n    SetArray.title = \"Set Array\";\r\n    SetArray.desc = \"Sets index of array\";\r\n\r\n    SetArray.prototype.onExecute = function() {\r\n        var arr = this.getInputData(0);\r\n\t\tif(!arr)\r\n\t\t\treturn;\r\n        var v = this.getInputData(1);\r\n\t\tif(v === undefined )\r\n\t\t\treturn;\r\n\t\tif(this.properties.index)\r\n\t\t\tarr[ Math.floor(this.properties.index) ] = v;\r\n\t\tthis.setOutputData(0,arr);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/set_array\", SetArray );\r\n\r\n    function ArrayElement() {\r\n        this.addInput(\"array\", \"array,table,string\");\r\n        this.addInput(\"index\", \"number\");\r\n        this.addOutput(\"value\", \"\");\r\n\t\tthis.addProperty(\"index\",0);\r\n    }\r\n\r\n    ArrayElement.title = \"Array[i]\";\r\n    ArrayElement.desc = \"Returns an element from an array\";\r\n\r\n    ArrayElement.prototype.onExecute = function() {\r\n        var array = this.getInputData(0);\r\n        var index = this.getInputData(1);\r\n\t\tif(index == null)\r\n\t\t\tindex = this.properties.index;\r\n\t\tif(array == null || index == null )\r\n\t\t\treturn;\r\n        this.setOutputData(0, array[Math.floor(Number(index))] );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/array[]\", ArrayElement);\r\n\r\n    function TableElement() {\r\n        this.addInput(\"table\", \"table\");\r\n        this.addInput(\"row\", \"number\");\r\n        this.addInput(\"col\", \"number\");\r\n        this.addOutput(\"value\", \"\");\r\n\t\tthis.addProperty(\"row\",0);\r\n\t\tthis.addProperty(\"column\",0);\r\n    }\r\n\r\n    TableElement.title = \"Table[row][col]\";\r\n    TableElement.desc = \"Returns an element from a table\";\r\n\r\n    TableElement.prototype.onExecute = function() {\r\n        var table = this.getInputData(0);\r\n        var row = this.getInputData(1);\r\n        var col = this.getInputData(2);\r\n\t\tif(row == null)\r\n\t\t\trow = this.properties.row;\r\n\t\tif(col == null)\r\n\t\t\tcol = this.properties.column;\r\n\t\tif(table == null || row == null || col == null)\r\n\t\t\treturn;\r\n\t\tvar row = table[Math.floor(Number(row))];\r\n\t\tif(row)\r\n\t        this.setOutputData(0, row[Math.floor(Number(col))] );\r\n\t\telse\r\n\t        this.setOutputData(0, null );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/table[][]\", TableElement);\r\n\r\n    function ObjectProperty() {\r\n        this.addInput(\"obj\", \"object\");\r\n        this.addOutput(\"property\", 0);\r\n        this.addProperty(\"value\", 0);\r\n        this.widget = this.addWidget(\"text\",\"prop.\",\"\",this.setValue.bind(this) );\r\n        this.widgets_up = true;\r\n        this.size = [140, 30];\r\n        this._value = null;\r\n    }\r\n\r\n    ObjectProperty.title = \"Object property\";\r\n    ObjectProperty.desc = \"Outputs the property of an object\";\r\n\r\n    ObjectProperty.prototype.setValue = function(v) {\r\n        this.properties.value = v;\r\n        this.widget.value = v;\r\n    };\r\n\r\n    ObjectProperty.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return \"in.\" + this.properties.value;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    ObjectProperty.prototype.onPropertyChanged = function(name, value) {\r\n        this.widget.value = value;\r\n    };\r\n\r\n    ObjectProperty.prototype.onExecute = function() {\r\n        var data = this.getInputData(0);\r\n        if (data != null) {\r\n            this.setOutputData(0, data[this.properties.value]);\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/object_property\", ObjectProperty);\r\n\r\n    function ObjectKeys() {\r\n        this.addInput(\"obj\", \"\");\r\n        this.addOutput(\"keys\", \"array\");\r\n        this.size = [140, 30];\r\n    }\r\n\r\n    ObjectKeys.title = \"Object keys\";\r\n    ObjectKeys.desc = \"Outputs an array with the keys of an object\";\r\n\r\n    ObjectKeys.prototype.onExecute = function() {\r\n        var data = this.getInputData(0);\r\n        if (data != null) {\r\n            this.setOutputData(0, Object.keys(data) );\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/object_keys\", ObjectKeys);\r\n\r\n\r\n\tfunction SetObject()\r\n\t{\r\n        this.addInput(\"obj\", \"\");\r\n        this.addInput(\"value\", \"\");\r\n        this.addOutput(\"obj\", \"\");\r\n\t\tthis.properties = { property: \"\" };\r\n        this.name_widget = this.addWidget(\"text\",\"prop.\",this.properties.property,\"property\");\r\n\t}\r\n\r\n    SetObject.title = \"Set Object\";\r\n    SetObject.desc = \"Adds propertiesrty to object\";\r\n\r\n    SetObject.prototype.onExecute = function() {\r\n        var obj = this.getInputData(0);\r\n\t\tif(!obj)\r\n\t\t\treturn;\r\n        var v = this.getInputData(1);\r\n\t\tif(v === undefined )\r\n\t\t\treturn;\r\n\t\tif(this.properties.property)\r\n\t\t\tobj[ this.properties.property ] = v;\r\n\t\tthis.setOutputData(0,obj);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/set_object\", SetObject );\r\n\r\n\r\n    function MergeObjects() {\r\n        this.addInput(\"A\", \"object\");\r\n        this.addInput(\"B\", \"object\");\r\n        this.addOutput(\"out\", \"object\");\r\n\t\tthis._result = {};\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"clear\",\"\",function(){\r\n\t\t\tthat._result = {};\r\n\t\t});\r\n\t\tthis.size = this.computeSize();\r\n    }\r\n\r\n    MergeObjects.title = \"Merge Objects\";\r\n    MergeObjects.desc = \"Creates an object copying properties from others\";\r\n\r\n    MergeObjects.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        var B = this.getInputData(1);\r\n\t\tvar C = this._result;\r\n\t\tif(A)\r\n\t\t\tfor(var i in A)\r\n\t\t\t\tC[i] = A[i];\r\n\t\tif(B)\r\n\t\t\tfor(var i in B)\r\n\t\t\t\tC[i] = B[i];\r\n\t\tthis.setOutputData(0,C);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/merge_objects\", MergeObjects );\r\n\r\n    //Store as variable\r\n    function Variable() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"in\");\r\n        this.addOutput(\"out\");\r\n\t\tthis.properties = { varname: \"myname\", container: Variable.LITEGRAPH };\r\n        this.value = null;\r\n    }\r\n\r\n    Variable.title = \"Variable\";\r\n    Variable.desc = \"store/read variable value\";\r\n\r\n\tVariable.LITEGRAPH = 0; //between all graphs\r\n\tVariable.GRAPH = 1;\t//only inside this graph\r\n\tVariable.GLOBALSCOPE = 2;\t//attached to Window\r\n\r\n    Variable[\"@container\"] = { type: \"enum\", values: {\"litegraph\":Variable.LITEGRAPH, \"graph\":Variable.GRAPH,\"global\": Variable.GLOBALSCOPE} };\r\n\r\n    Variable.prototype.onExecute = function() {\r\n\t\tvar container = this.getContainer();\r\n\r\n\t\tif(this.isInputConnected(0))\r\n\t\t{\r\n\t\t\tthis.value = this.getInputData(0);\r\n\t\t\tcontainer[ this.properties.varname ] = this.value;\r\n\t\t\tthis.setOutputData(0, this.value );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData( 0, container[ this.properties.varname ] );\r\n    };\r\n\r\n\tVariable.prototype.getContainer = function()\r\n\t{\r\n\t\tswitch(this.properties.container)\r\n\t\t{\r\n\t\t\tcase Variable.GRAPH:\r\n\t\t\t\tif(this.graph)\r\n\t\t\t\t\treturn this.graph.vars;\r\n\t\t\t\treturn {};\r\n\t\t\t\tbreak;\r\n\t\t\tcase Variable.GLOBALSCOPE:\r\n\t\t\t\treturn global;\r\n\t\t\t\tbreak;\r\n\t\t\tcase Variable.LITEGRAPH:\r\n\t\t\tdefault:\r\n\t\t\t\treturn LiteGraph.Globals;\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n    Variable.prototype.getTitle = function() {\r\n        return this.properties.varname;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/variable\", Variable);\r\n\r\n    function length(v) {\r\n        if(v && v.length != null)\r\n\t\t\treturn Number(v.length);\r\n\t\treturn 0;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"basic/length\",\r\n        length,\r\n        [\"\"],\r\n        \"number\"\r\n    );\r\n\r\n    function length(v) {\r\n        if(v && v.length != null)\r\n\t\t\treturn Number(v.length);\r\n\t\treturn 0;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"basic/not\",\r\n        function(a){ return !a; },\r\n        [\"\"],\r\n        \"boolean\"\r\n    );\r\n\r\n\tfunction DownloadData() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"data\", 0 );\r\n        this.addInput(\"download\", LiteGraph.ACTION );\r\n\t\tthis.properties = { filename: \"data.json\" };\r\n        this.value = null;\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"Download\",\"\", function(v){\r\n\t\t\tif(!that.value)\r\n\t\t\t\treturn;\r\n\t\t\tthat.downloadAsFile();\r\n\t\t});\r\n    }\r\n\r\n    DownloadData.title = \"Download\";\r\n    DownloadData.desc = \"Download some data\";\r\n\r\n\tDownloadData.prototype.downloadAsFile = function()\r\n\t{\r\n\t\tif(this.value == null)\r\n\t\t\treturn;\r\n\r\n\t\tvar str = null;\r\n\t\tif(this.value.constructor === String)\r\n\t\t\tstr = this.value;\r\n\t\telse\r\n\t\t\tstr = JSON.stringify(this.value);\r\n\r\n\t\tvar file = new Blob([str]);\r\n\t\tvar url = URL.createObjectURL( file );\r\n\t\tvar element = document.createElement(\"a\");\r\n\t\telement.setAttribute('href', url);\r\n\t\telement.setAttribute('download', this.properties.filename );\r\n\t\telement.style.display = 'none';\r\n\t\tdocument.body.appendChild(element);\r\n\t\telement.click();\r\n\t\tdocument.body.removeChild(element);\r\n\t\tsetTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url\r\n\t}\r\n\r\n    DownloadData.prototype.onAction = function(action, param) {\r\n\t\tvar that = this;\r\n\t\tsetTimeout( function(){ that.downloadAsFile(); }, 100); //deferred to avoid blocking the renderer with the popup\r\n\t}\r\n\r\n    DownloadData.prototype.onExecute = function() {\r\n        if (this.inputs[0]) {\r\n            this.value = this.getInputData(0);\r\n        }\r\n    };\r\n\r\n    DownloadData.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.filename;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/download\", DownloadData);\r\n\r\n\r\n\r\n    //Watch a value in the editor\r\n    function Watch() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"value\", 0, { label: \"\" });\r\n        this.value = 0;\r\n    }\r\n\r\n    Watch.title = \"Watch\";\r\n    Watch.desc = \"Show value of input\";\r\n\r\n    Watch.prototype.onExecute = function() {\r\n        if (this.inputs[0]) {\r\n            this.value = this.getInputData(0);\r\n        }\r\n    };\r\n\r\n    Watch.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.inputs[0].label;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    Watch.toString = function(o) {\r\n        if (o == null) {\r\n            return \"null\";\r\n        } else if (o.constructor === Number) {\r\n            return o.toFixed(3);\r\n        } else if (o.constructor === Array) {\r\n            var str = \"[\";\r\n            for (var i = 0; i < o.length; ++i) {\r\n                str += Watch.toString(o[i]) + (i + 1 != o.length ? \",\" : \"\");\r\n            }\r\n            str += \"]\";\r\n            return str;\r\n        } else {\r\n            return String(o);\r\n        }\r\n    };\r\n\r\n    Watch.prototype.onDrawBackground = function(ctx) {\r\n        //show the current value\r\n        this.inputs[0].label = Watch.toString(this.value);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/watch\", Watch);\r\n\r\n    //in case one type doesnt match other type but you want to connect them anyway\r\n    function Cast() {\r\n        this.addInput(\"in\", 0);\r\n        this.addOutput(\"out\", 0);\r\n        this.size = [40, 30];\r\n    }\r\n\r\n    Cast.title = \"Cast\";\r\n    Cast.desc = \"Allows to connect different types\";\r\n\r\n    Cast.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.getInputData(0));\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/cast\", Cast);\r\n\r\n    //Show value inside the debug console\r\n    function Console() {\r\n        this.mode = LiteGraph.ON_EVENT;\r\n        this.size = [80, 30];\r\n        this.addProperty(\"msg\", \"\");\r\n        this.addInput(\"log\", LiteGraph.EVENT);\r\n        this.addInput(\"msg\", 0);\r\n    }\r\n\r\n    Console.title = \"Console\";\r\n    Console.desc = \"Show value inside the console\";\r\n\r\n    Console.prototype.onAction = function(action, param) {\r\n        // param is the action\r\n        var msg = this.getInputData(1); //getInputDataByName(\"msg\");\r\n        //if (msg == null || typeof msg == \"undefined\") return;\r\n        if (!msg) msg = this.properties.msg;\r\n        if (!msg) msg = \"Event: \"+param; // msg is undefined if the slot is lost?\r\n        if (action == \"log\") {\r\n            console.log(msg);\r\n        } else if (action == \"warn\") {\r\n            console.warn(msg);\r\n        } else if (action == \"error\") {\r\n            console.error(msg);\r\n        }\r\n    };\r\n\r\n    Console.prototype.onExecute = function() {\r\n        var msg = this.getInputData(1); //getInputDataByName(\"msg\");\r\n        if (!msg) msg = this.properties.msg;\r\n        if (msg != null && typeof msg != \"undefined\") {\r\n            this.properties.msg = msg;\r\n            console.log(msg);\r\n        }\r\n    };\r\n\r\n    Console.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"log\", LiteGraph.ACTION],\r\n            [\"warn\", LiteGraph.ACTION],\r\n            [\"error\", LiteGraph.ACTION]\r\n        ];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/console\", Console);\r\n\r\n    //Show value inside the debug console\r\n    function Alert() {\r\n        this.mode = LiteGraph.ON_EVENT;\r\n        this.addProperty(\"msg\", \"\");\r\n        this.addInput(\"\", LiteGraph.EVENT);\r\n        var that = this;\r\n        this.widget = this.addWidget(\"text\", \"Text\", \"\", \"msg\");\r\n        this.widgets_up = true;\r\n        this.size = [200, 30];\r\n    }\r\n\r\n    Alert.title = \"Alert\";\r\n    Alert.desc = \"Show an alert window\";\r\n    Alert.color = \"#510\";\r\n\r\n    Alert.prototype.onConfigure = function(o) {\r\n        this.widget.value = o.properties.msg;\r\n    };\r\n\r\n    Alert.prototype.onAction = function(action, param) {\r\n        var msg = this.properties.msg;\r\n        setTimeout(function() {\r\n            alert(msg);\r\n        }, 10);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/alert\", Alert);\r\n\r\n    //Execites simple code\r\n    function NodeScript() {\r\n        this.size = [60, 30];\r\n        this.addProperty(\"onExecute\", \"return A;\");\r\n        this.addInput(\"A\", 0);\r\n        this.addInput(\"B\", 0);\r\n        this.addOutput(\"out\", 0);\r\n\r\n        this._func = null;\r\n        this.data = {};\r\n    }\r\n\r\n    NodeScript.prototype.onConfigure = function(o) {\r\n        if (o.properties.onExecute && LiteGraph.allow_scripts)\r\n            this.compileCode(o.properties.onExecute);\r\n\t\telse\r\n\t\t\tconsole.warn(\"Script not compiled, LiteGraph.allow_scripts is false\");\r\n    };\r\n\r\n    NodeScript.title = \"Script\";\r\n    NodeScript.desc = \"executes a code (max 256 characters)\";\r\n\r\n    NodeScript.widgets_info = {\r\n        onExecute: { type: \"code\" }\r\n    };\r\n\r\n    NodeScript.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"onExecute\" && LiteGraph.allow_scripts)\r\n            this.compileCode(value);\r\n\t\telse\r\n\t\t\tconsole.warn(\"Script not compiled, LiteGraph.allow_scripts is false\");\r\n    };\r\n\r\n    NodeScript.prototype.compileCode = function(code) {\r\n        this._func = null;\r\n        if (code.length > 256) {\r\n            console.warn(\"Script too long, max 256 chars\");\r\n        } else {\r\n            var code_low = code.toLowerCase();\r\n            var forbidden_words = [\r\n                \"script\",\r\n                \"body\",\r\n                \"document\",\r\n                \"eval\",\r\n                \"nodescript\",\r\n                \"function\"\r\n            ]; //bad security solution\r\n            for (var i = 0; i < forbidden_words.length; ++i) {\r\n                if (code_low.indexOf(forbidden_words[i]) != -1) {\r\n                    console.warn(\"invalid script\");\r\n                    return;\r\n                }\r\n            }\r\n            try {\r\n                this._func = new Function(\"A\", \"B\", \"C\", \"DATA\", \"node\", code);\r\n            } catch (err) {\r\n                console.error(\"Error parsing script\");\r\n                console.error(err);\r\n            }\r\n        }\r\n    };\r\n\r\n    NodeScript.prototype.onExecute = function() {\r\n        if (!this._func) {\r\n            return;\r\n        }\r\n\r\n        try {\r\n            var A = this.getInputData(0);\r\n            var B = this.getInputData(1);\r\n            var C = this.getInputData(2);\r\n            this.setOutputData(0, this._func(A, B, C, this.data, this));\r\n        } catch (err) {\r\n            console.error(\"Error in script\");\r\n            console.error(err);\r\n        }\r\n    };\r\n\r\n    NodeScript.prototype.onGetOutputs = function() {\r\n        return [[\"C\", \"\"]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/script\", NodeScript);\r\n    \r\n    \r\n    function GenericCompare() {\r\n        this.addInput(\"A\", 0);\r\n        this.addInput(\"B\", 0);\r\n        this.addOutput(\"true\", \"boolean\");\r\n        this.addOutput(\"false\", \"boolean\");\r\n        this.addProperty(\"A\", 1);\r\n        this.addProperty(\"B\", 1);\r\n        this.addProperty(\"OP\", \"==\", \"enum\", { values: GenericCompare.values });\r\n\t\tthis.addWidget(\"combo\",\"Op.\",this.properties.OP,{ property: \"OP\", values: GenericCompare.values } );\r\n\r\n        this.size = [80, 60];\r\n    }\r\n\r\n    GenericCompare.values = [\"==\", \"!=\"]; //[\">\", \"<\", \"==\", \"!=\", \"<=\", \">=\", \"||\", \"&&\" ];\r\n    GenericCompare[\"@OP\"] = {\r\n        type: \"enum\",\r\n        title: \"operation\",\r\n        values: GenericCompare.values\r\n    };\r\n\r\n    GenericCompare.title = \"Compare *\";\r\n    GenericCompare.desc = \"evaluates condition between A and B\";\r\n\r\n    GenericCompare.prototype.getTitle = function() {\r\n        return \"*A \" + this.properties.OP + \" *B\";\r\n    };\r\n\r\n    GenericCompare.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        if (A === undefined) {\r\n            A = this.properties.A;\r\n        } else {\r\n            this.properties.A = A;\r\n        }\r\n\r\n        var B = this.getInputData(1);\r\n        if (B === undefined) {\r\n            B = this.properties.B;\r\n        } else {\r\n            this.properties.B = B;\r\n        }\r\n\r\n        var result = false;\r\n        if (typeof A == typeof B){\r\n            switch (this.properties.OP) {\r\n                case \"==\":\r\n                case \"!=\":\r\n                    // traverse both objects.. consider that this is not a true deep check! consider underscore or other library for thath :: _isEqual()\r\n                    result = true;\r\n                    switch(typeof A){\r\n                        case \"object\":\r\n                            var aProps = Object.getOwnPropertyNames(A);\r\n                            var bProps = Object.getOwnPropertyNames(B);\r\n                            if (aProps.length != bProps.length){\r\n                                result = false;\r\n                                break;\r\n                            }\r\n                            for (var i = 0; i < aProps.length; i++) {\r\n                                var propName = aProps[i];\r\n                                if (A[propName] !== B[propName]) {\r\n                                    result = false;\r\n                                    break;\r\n                                }\r\n                            }\r\n                        break;\r\n                        default:\r\n                            result = A == B;\r\n                    }\r\n                    if (this.properties.OP == \"!=\") result = !result;\r\n                    break;\r\n                /*case \">\":\r\n                    result = A > B;\r\n                    break;\r\n                case \"<\":\r\n                    result = A < B;\r\n                    break;\r\n                case \"<=\":\r\n                    result = A <= B;\r\n                    break;\r\n                case \">=\":\r\n                    result = A >= B;\r\n                    break;\r\n                case \"||\":\r\n                    result = A || B;\r\n                    break;\r\n                case \"&&\":\r\n                    result = A && B;\r\n                    break;*/\r\n            }\r\n        }\r\n        this.setOutputData(0, result);\r\n        this.setOutputData(1, !result);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/CompareValues\", GenericCompare);\r\n    \r\n})(this);\r\n\n//event related nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    //Show value inside the debug console\r\n    function LogEvent() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"event\", LiteGraph.ACTION);\r\n    }\r\n\r\n    LogEvent.title = \"Log Event\";\r\n    LogEvent.desc = \"Log event in console\";\r\n\r\n    LogEvent.prototype.onAction = function(action, param, options) {\r\n        console.log(action, param);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/log\", LogEvent);\r\n\r\n    //convert to Event if the value is true\r\n    function TriggerEvent() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"if\", \"\");\r\n        this.addOutput(\"true\", LiteGraph.EVENT);\r\n        this.addOutput(\"change\", LiteGraph.EVENT);\r\n        this.addOutput(\"false\", LiteGraph.EVENT);\r\n\t\tthis.properties = { only_on_change: true };\r\n\t\tthis.prev = 0;\r\n    }\r\n\r\n    TriggerEvent.title = \"TriggerEvent\";\r\n    TriggerEvent.desc = \"Triggers event if input evaluates to true\";\r\n\r\n    TriggerEvent.prototype.onExecute = function( param, options) {\r\n\t\tvar v = this.getInputData(0);\r\n\t\tvar changed = (v != this.prev);\r\n\t\tif(this.prev === 0)\r\n\t\t\tchanged = false;\r\n\t\tvar must_resend = (changed && this.properties.only_on_change) || (!changed && !this.properties.only_on_change);\r\n\t\tif(v && must_resend )\r\n\t        this.triggerSlot(0, param, null, options);\r\n\t\tif(!v && must_resend)\r\n\t        this.triggerSlot(2, param, null, options);\r\n\t\tif(changed)\r\n\t        this.triggerSlot(1, param, null, options);\r\n\t\tthis.prev = v;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/trigger\", TriggerEvent);\r\n\r\n    //Sequence of events\r\n    function Sequence() {\r\n\t\tvar that = this;\r\n        this.addInput(\"\", LiteGraph.ACTION);\r\n        this.addInput(\"\", LiteGraph.ACTION);\r\n        this.addInput(\"\", LiteGraph.ACTION);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addWidget(\"button\",\"+\",null,function(){\r\n\t        that.addInput(\"\", LiteGraph.ACTION);\r\n\t        that.addOutput(\"\", LiteGraph.EVENT);\r\n        });\r\n        this.size = [90, 70];\r\n        this.flags = { horizontal: true, render_box: false };\r\n    }\r\n\r\n    Sequence.title = \"Sequence\";\r\n    Sequence.desc = \"Triggers a sequence of events when an event arrives\";\r\n\r\n    Sequence.prototype.getTitle = function() {\r\n        return \"\";\r\n    };\r\n\r\n    Sequence.prototype.onAction = function(action, param, options) {\r\n        if (this.outputs) {\r\n            options = options || {};\r\n            for (var i = 0; i < this.outputs.length; ++i) {\r\n\t\t\t\tvar output = this.outputs[i];\r\n\t\t\t\t//needs more info about this...\r\n\t\t\t\tif( options.action_call ) // CREATE A NEW ID FOR THE ACTION\r\n\t                options.action_call = options.action_call + \"_seq_\" + i;\r\n\t\t\t\telse\r\n\t\t\t\t\toptions.action_call = this.id + \"_\" + (action ? action : \"action\")+\"_seq_\"+i+\"_\"+Math.floor(Math.random()*9999);\r\n                this.triggerSlot(i, param, null, options);\r\n            }\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/sequence\", Sequence);\r\n\r\n\r\n   //Sequence of events\r\n   function WaitAll() {\r\n    var that = this;\r\n    this.addInput(\"\", LiteGraph.ACTION);\r\n    this.addInput(\"\", LiteGraph.ACTION);\r\n    this.addOutput(\"\", LiteGraph.EVENT);\r\n    this.addWidget(\"button\",\"+\",null,function(){\r\n        that.addInput(\"\", LiteGraph.ACTION);\r\n        that.size[0] = 90;\r\n    });\r\n    this.size = [90, 70];\r\n    this.ready = [];\r\n}\r\n\r\nWaitAll.title = \"WaitAll\";\r\nWaitAll.desc = \"Wait until all input events arrive then triggers output\";\r\n\r\nWaitAll.prototype.getTitle = function() {\r\n    return \"\";\r\n};\r\n\r\nWaitAll.prototype.onDrawBackground = function(ctx)\r\n{\r\n    if (this.flags.collapsed) {\r\n        return;\r\n    }\r\n    for(var i = 0; i < this.inputs.length; ++i)\r\n    {\r\n        var y = i * LiteGraph.NODE_SLOT_HEIGHT + 10;\r\n        ctx.fillStyle = this.ready[i] ? \"#AFB\" : \"#000\";\r\n        ctx.fillRect(20, y, 10, 10);\r\n    }\r\n}\r\n\r\nWaitAll.prototype.onAction = function(action, param, options, slot_index) {\r\n    if(slot_index == null)\r\n        return;\r\n\r\n    //check all\r\n    this.ready.length = this.outputs.length;\r\n    this.ready[slot_index] = true;\r\n    for(var i = 0; i < this.ready.length;++i)\r\n        if(!this.ready[i])\r\n            return;\r\n    //pass\r\n    this.reset();\r\n    this.triggerSlot(0);\r\n};\r\n\r\nWaitAll.prototype.reset = function()\r\n{\r\n    this.ready.length = 0;\r\n}\r\n\r\nLiteGraph.registerNodeType(\"events/waitAll\", WaitAll);    \r\n\r\n\r\n    //Sequencer for events\r\n    function Stepper() {\r\n\t\tvar that = this;\r\n\t\tthis.properties = { index: 0 };\r\n        this.addInput(\"index\", \"number\");\r\n        this.addInput(\"step\", LiteGraph.ACTION);\r\n        this.addInput(\"reset\", LiteGraph.ACTION);\r\n        this.addOutput(\"index\", \"number\");\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT,{removable:true});\r\n        this.addWidget(\"button\",\"+\",null,function(){\r\n\t        that.addOutput(\"\", LiteGraph.EVENT, {removable:true});\r\n        });\r\n        this.size = [120, 120];\r\n        this.flags = { render_box: false };\r\n    }\r\n\r\n    Stepper.title = \"Stepper\";\r\n    Stepper.desc = \"Trigger events sequentially when an tick arrives\";\r\n\r\n\tStepper.prototype.onDrawBackground = function(ctx)\r\n\t{\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\t\tvar index = this.properties.index || 0;\r\n        ctx.fillStyle = \"#AFB\";\r\n\t\tvar w = this.size[0];\r\n        var y = (index + 1)* LiteGraph.NODE_SLOT_HEIGHT + 4;\r\n        ctx.beginPath();\r\n        ctx.moveTo(w - 30, y);\r\n        ctx.lineTo(w - 30, y + LiteGraph.NODE_SLOT_HEIGHT);\r\n        ctx.lineTo(w - 15, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5);\r\n        ctx.fill();\r\n\t}\r\n\r\n\tStepper.prototype.onExecute = function()\r\n\t{\r\n\t\tvar index = this.getInputData(0);\r\n\t\tif(index != null)\r\n\t\t{\r\n\t\t\tindex = Math.floor(index);\r\n\t\t\tindex = clamp( index, 0, this.outputs ? (this.outputs.length - 2) : 0 );\r\n\t\t\tif( index != this.properties.index )\r\n\t\t\t{\r\n\t\t\t\tthis.properties.index = index;\r\n\t\t\t    this.triggerSlot( index+1 );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0, this.properties.index );\r\n\t}\r\n\r\n    Stepper.prototype.onAction = function(action, param) {\r\n\t\tif(action == \"reset\")\r\n\t\t\tthis.properties.index = 0;\r\n\t\telse if(action == \"step\")\r\n\t\t{\r\n            this.triggerSlot(this.properties.index+1, param);\r\n\t\t\tvar n = this.outputs ? this.outputs.length - 1 : 0;\r\n\t\t\tthis.properties.index = (this.properties.index + 1) % n;\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/stepper\", Stepper);\r\n\r\n    //Filter events\r\n    function FilterEvent() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"event\", LiteGraph.ACTION);\r\n        this.addOutput(\"event\", LiteGraph.EVENT);\r\n        this.properties = {\r\n            equal_to: \"\",\r\n            has_property: \"\",\r\n            property_equal_to: \"\"\r\n        };\r\n    }\r\n\r\n    FilterEvent.title = \"Filter Event\";\r\n    FilterEvent.desc = \"Blocks events that do not match the filter\";\r\n\r\n    FilterEvent.prototype.onAction = function(action, param, options) {\r\n        if (param == null) {\r\n            return;\r\n        }\r\n\r\n        if (this.properties.equal_to && this.properties.equal_to != param) {\r\n            return;\r\n        }\r\n\r\n        if (this.properties.has_property) {\r\n            var prop = param[this.properties.has_property];\r\n            if (prop == null) {\r\n                return;\r\n            }\r\n\r\n            if (\r\n                this.properties.property_equal_to &&\r\n                this.properties.property_equal_to != prop\r\n            ) {\r\n                return;\r\n            }\r\n        }\r\n\r\n        this.triggerSlot(0, param, null, options);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/filter\", FilterEvent);\r\n\r\n\r\n    function EventBranch() {\r\n        this.addInput(\"in\", LiteGraph.ACTION);\r\n        this.addInput(\"cond\", \"boolean\");\r\n        this.addOutput(\"true\", LiteGraph.EVENT);\r\n        this.addOutput(\"false\", LiteGraph.EVENT);\r\n        this.size = [120, 60];\r\n\t\tthis._value = false;\r\n    }\r\n\r\n    EventBranch.title = \"Branch\";\r\n    EventBranch.desc = \"If condition is true, outputs triggers true, otherwise false\";\r\n\r\n    EventBranch.prototype.onExecute = function() {\r\n\t\tthis._value = this.getInputData(1);\r\n\t}\r\n\r\n    EventBranch.prototype.onAction = function(action, param, options) {\r\n        this._value = this.getInputData(1);\r\n\t\tthis.triggerSlot(this._value ? 0 : 1, param, null, options);\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"events/branch\", EventBranch);\r\n\r\n    //Show value inside the debug console\r\n    function EventCounter() {\r\n        this.addInput(\"inc\", LiteGraph.ACTION);\r\n        this.addInput(\"dec\", LiteGraph.ACTION);\r\n        this.addInput(\"reset\", LiteGraph.ACTION);\r\n        this.addOutput(\"change\", LiteGraph.EVENT);\r\n        this.addOutput(\"num\", \"number\");\r\n        this.addProperty(\"doCountExecution\", false, \"boolean\", {name: \"Count Executions\"});\r\n        this.addWidget(\"toggle\",\"Count Exec.\",this.properties.doCountExecution,\"doCountExecution\");\r\n        this.num = 0;\r\n    }\r\n\r\n    EventCounter.title = \"Counter\";\r\n    EventCounter.desc = \"Counts events\";\r\n\r\n    EventCounter.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return String(this.num);\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    EventCounter.prototype.onAction = function(action, param, options) {\r\n        var v = this.num;\r\n        if (action == \"inc\") {\r\n            this.num += 1;\r\n        } else if (action == \"dec\") {\r\n            this.num -= 1;\r\n        } else if (action == \"reset\") {\r\n            this.num = 0;\r\n        }\r\n        if (this.num != v) {\r\n            this.trigger(\"change\", this.num);\r\n        }\r\n    };\r\n\r\n    EventCounter.prototype.onDrawBackground = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n        ctx.fillStyle = \"#AAA\";\r\n        ctx.font = \"20px Arial\";\r\n        ctx.textAlign = \"center\";\r\n        ctx.fillText(this.num, this.size[0] * 0.5, this.size[1] * 0.5);\r\n    };\r\n\r\n    EventCounter.prototype.onExecute = function() {\r\n        if(this.properties.doCountExecution){\r\n            this.num += 1;\r\n        }\r\n        this.setOutputData(1, this.num);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/counter\", EventCounter);\r\n\r\n    //Show value inside the debug console\r\n    function DelayEvent() {\r\n        this.size = [60, 30];\r\n        this.addProperty(\"time_in_ms\", 1000);\r\n        this.addInput(\"event\", LiteGraph.ACTION);\r\n        this.addOutput(\"on_time\", LiteGraph.EVENT);\r\n\r\n        this._pending = [];\r\n    }\r\n\r\n    DelayEvent.title = \"Delay\";\r\n    DelayEvent.desc = \"Delays one event\";\r\n\r\n    DelayEvent.prototype.onAction = function(action, param, options) {\r\n        var time = this.properties.time_in_ms;\r\n        if (time <= 0) {\r\n            this.trigger(null, param, options);\r\n        } else {\r\n            this._pending.push([time, param]);\r\n        }\r\n    };\r\n\r\n    DelayEvent.prototype.onExecute = function(param, options) {\r\n        var dt = this.graph.elapsed_time * 1000; //in ms\r\n\r\n        if (this.isInputConnected(1)) {\r\n            this.properties.time_in_ms = this.getInputData(1);\r\n        }\r\n\r\n        for (var i = 0; i < this._pending.length; ++i) {\r\n            var actionPass = this._pending[i];\r\n            actionPass[0] -= dt;\r\n            if (actionPass[0] > 0) {\r\n                continue;\r\n            }\r\n\r\n            //remove\r\n            this._pending.splice(i, 1);\r\n            --i;\r\n\r\n            //trigger\r\n            this.trigger(null, actionPass[1], options);\r\n        }\r\n    };\r\n\r\n    DelayEvent.prototype.onGetInputs = function() {\r\n        return [[\"event\", LiteGraph.ACTION], [\"time_in_ms\", \"number\"]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/delay\", DelayEvent);\r\n\r\n    //Show value inside the debug console\r\n    function TimerEvent() {\r\n        this.addProperty(\"interval\", 1000);\r\n        this.addProperty(\"event\", \"tick\");\r\n        this.addOutput(\"on_tick\", LiteGraph.EVENT);\r\n        this.time = 0;\r\n        this.last_interval = 1000;\r\n        this.triggered = false;\r\n    }\r\n\r\n    TimerEvent.title = \"Timer\";\r\n    TimerEvent.desc = \"Sends an event every N milliseconds\";\r\n\r\n    TimerEvent.prototype.onStart = function() {\r\n        this.time = 0;\r\n    };\r\n\r\n    TimerEvent.prototype.getTitle = function() {\r\n        return \"Timer: \" + this.last_interval.toString() + \"ms\";\r\n    };\r\n\r\n    TimerEvent.on_color = \"#AAA\";\r\n    TimerEvent.off_color = \"#222\";\r\n\r\n    TimerEvent.prototype.onDrawBackground = function() {\r\n        this.boxcolor = this.triggered\r\n            ? TimerEvent.on_color\r\n            : TimerEvent.off_color;\r\n        this.triggered = false;\r\n    };\r\n\r\n    TimerEvent.prototype.onExecute = function() {\r\n        var dt = this.graph.elapsed_time * 1000; //in ms\r\n\r\n        var trigger = this.time == 0;\r\n\r\n        this.time += dt;\r\n        this.last_interval = Math.max(\r\n            1,\r\n            this.getInputOrProperty(\"interval\") | 0\r\n        );\r\n\r\n        if (\r\n            !trigger &&\r\n            (this.time < this.last_interval || isNaN(this.last_interval))\r\n        ) {\r\n            if (this.inputs && this.inputs.length > 1 && this.inputs[1]) {\r\n                this.setOutputData(1, false);\r\n            }\r\n            return;\r\n        }\r\n\r\n        this.triggered = true;\r\n        this.time = this.time % this.last_interval;\r\n        this.trigger(\"on_tick\", this.properties.event);\r\n        if (this.inputs && this.inputs.length > 1 && this.inputs[1]) {\r\n            this.setOutputData(1, true);\r\n        }\r\n    };\r\n\r\n    TimerEvent.prototype.onGetInputs = function() {\r\n        return [[\"interval\", \"number\"]];\r\n    };\r\n\r\n    TimerEvent.prototype.onGetOutputs = function() {\r\n        return [[\"tick\", \"boolean\"]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/timer\", TimerEvent);\r\n\r\n\r\n\r\n    function SemaphoreEvent() {\r\n        this.addInput(\"go\", LiteGraph.ACTION );\r\n        this.addInput(\"green\", LiteGraph.ACTION );\r\n        this.addInput(\"red\", LiteGraph.ACTION );\r\n        this.addOutput(\"continue\", LiteGraph.EVENT );\r\n        this.addOutput(\"blocked\", LiteGraph.EVENT );\r\n        this.addOutput(\"is_green\", \"boolean\" );\r\n\t\tthis._ready = false;\r\n\t\tthis.properties = {};\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"reset\",\"\",function(){\r\n\t\t\tthat._ready = false;\r\n\t\t});\r\n    }\r\n\r\n    SemaphoreEvent.title = \"Semaphore Event\";\r\n    SemaphoreEvent.desc = \"Until both events are not triggered, it doesnt continue.\";\r\n\r\n\tSemaphoreEvent.prototype.onExecute = function()\r\n\t{\r\n\t\tthis.setOutputData(1,this._ready);\r\n\t\tthis.boxcolor = this._ready ? \"#9F9\" : \"#FA5\";\r\n\t}\r\n\r\n    SemaphoreEvent.prototype.onAction = function(action, param) {\r\n\t\tif( action == \"go\" )\r\n\t\t\tthis.triggerSlot( this._ready ? 0 : 1 );\r\n\t\telse if( action == \"green\" )\r\n\t\t\tthis._ready = true;\r\n\t\telse if( action == \"red\" )\r\n\t\t\tthis._ready = false;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/semaphore\", SemaphoreEvent);\r\n\r\n    function OnceEvent() {\r\n        this.addInput(\"in\", LiteGraph.ACTION );\r\n        this.addInput(\"reset\", LiteGraph.ACTION );\r\n        this.addOutput(\"out\", LiteGraph.EVENT );\r\n\t\tthis._once = false;\r\n\t\tthis.properties = {};\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"reset\",\"\",function(){\r\n\t\t\tthat._once = false;\r\n\t\t});\r\n    }\r\n\r\n    OnceEvent.title = \"Once\";\r\n    OnceEvent.desc = \"Only passes an event once, then gets locked\";\r\n\r\n    OnceEvent.prototype.onAction = function(action, param) {\r\n\t\tif( action == \"in\" && !this._once )\r\n\t\t{\r\n\t\t\tthis._once = true;\r\n\t\t\tthis.triggerSlot( 0, param );\r\n\t\t}\r\n\t\telse if( action == \"reset\" )\r\n\t\t\tthis._once = false;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/once\", OnceEvent);\r\n\r\n    function DataStore() {\r\n        this.addInput(\"data\", 0);\r\n        this.addInput(\"assign\", LiteGraph.ACTION);\r\n        this.addOutput(\"data\", 0);\r\n\t\tthis._last_value = null;\r\n\t\tthis.properties = { data: null, serialize: true };\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"store\",\"\",function(){\r\n\t\t\tthat.properties.data = that._last_value;\r\n\t\t});\r\n    }\r\n\r\n    DataStore.title = \"Data Store\";\r\n    DataStore.desc = \"Stores data and only changes when event is received\";\r\n\r\n\tDataStore.prototype.onExecute = function()\r\n\t{\r\n\t\tthis._last_value = this.getInputData(0);\r\n\t\tthis.setOutputData(0, this.properties.data );\r\n\t}\r\n\r\n    DataStore.prototype.onAction = function(action, param, options) {\r\n\t\tthis.properties.data = this._last_value;\r\n    };\r\n\r\n\tDataStore.prototype.onSerialize = function(o)\r\n\t{\r\n\t\tif(o.data == null)\r\n\t\t\treturn;\r\n\t\tif(this.properties.serialize == false || (o.data.constructor !== String && o.data.constructor !== Number && o.data.constructor !== Boolean && o.data.constructor !== Array && o.data.constructor !== Object ))\r\n\t\t\to.data = null;\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/data_store\", DataStore);\r\n\r\n\r\n\r\n})(this);\r\n\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    function GamepadInput() {\r\n        this.addOutput(\"left_x_axis\", \"number\");\r\n        this.addOutput(\"left_y_axis\", \"number\");\r\n        this.addOutput(\"button_pressed\", LiteGraph.EVENT);\r\n        this.properties = { gamepad_index: 0, threshold: 0.1 };\r\n\r\n        this._left_axis = new Float32Array(2);\r\n        this._right_axis = new Float32Array(2);\r\n        this._triggers = new Float32Array(2);\r\n        this._previous_buttons = new Uint8Array(17);\r\n        this._current_buttons = new Uint8Array(17);\r\n    }\r\n\r\n    GamepadInput.title = \"Gamepad\";\r\n    GamepadInput.desc = \"gets the input of the gamepad\";\r\n\r\n    GamepadInput.CENTER = 0;\r\n    GamepadInput.LEFT = 1;\r\n    GamepadInput.RIGHT = 2;\r\n    GamepadInput.UP = 4;\r\n    GamepadInput.DOWN = 8;\r\n\r\n    GamepadInput.zero = new Float32Array(2);\r\n    GamepadInput.buttons = [\r\n        \"a\",\r\n        \"b\",\r\n        \"x\",\r\n        \"y\",\r\n        \"lb\",\r\n        \"rb\",\r\n        \"lt\",\r\n        \"rt\",\r\n        \"back\",\r\n        \"start\",\r\n        \"ls\",\r\n        \"rs\",\r\n        \"home\"\r\n    ];\r\n\r\n    GamepadInput.prototype.onExecute = function() {\r\n        //get gamepad\r\n        var gamepad = this.getGamepad();\r\n        var threshold = this.properties.threshold || 0.0;\r\n\r\n        if (gamepad) {\r\n            this._left_axis[0] =\r\n                Math.abs(gamepad.xbox.axes[\"lx\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"lx\"]\r\n                    : 0;\r\n            this._left_axis[1] =\r\n                Math.abs(gamepad.xbox.axes[\"ly\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"ly\"]\r\n                    : 0;\r\n            this._right_axis[0] =\r\n                Math.abs(gamepad.xbox.axes[\"rx\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"rx\"]\r\n                    : 0;\r\n            this._right_axis[1] =\r\n                Math.abs(gamepad.xbox.axes[\"ry\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"ry\"]\r\n                    : 0;\r\n            this._triggers[0] =\r\n                Math.abs(gamepad.xbox.axes[\"ltrigger\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"ltrigger\"]\r\n                    : 0;\r\n            this._triggers[1] =\r\n                Math.abs(gamepad.xbox.axes[\"rtrigger\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"rtrigger\"]\r\n                    : 0;\r\n        }\r\n\r\n        if (this.outputs) {\r\n            for (var i = 0; i < this.outputs.length; i++) {\r\n                var output = this.outputs[i];\r\n                if (!output.links || !output.links.length) {\r\n                    continue;\r\n                }\r\n                var v = null;\r\n\r\n                if (gamepad) {\r\n                    switch (output.name) {\r\n                        case \"left_axis\":\r\n                            v = this._left_axis;\r\n                            break;\r\n                        case \"right_axis\":\r\n                            v = this._right_axis;\r\n                            break;\r\n                        case \"left_x_axis\":\r\n                            v = this._left_axis[0];\r\n                            break;\r\n                        case \"left_y_axis\":\r\n                            v = this._left_axis[1];\r\n                            break;\r\n                        case \"right_x_axis\":\r\n                            v = this._right_axis[0];\r\n                            break;\r\n                        case \"right_y_axis\":\r\n                            v = this._right_axis[1];\r\n                            break;\r\n                        case \"trigger_left\":\r\n                            v = this._triggers[0];\r\n                            break;\r\n                        case \"trigger_right\":\r\n                            v = this._triggers[1];\r\n                            break;\r\n                        case \"a_button\":\r\n                            v = gamepad.xbox.buttons[\"a\"] ? 1 : 0;\r\n                            break;\r\n                        case \"b_button\":\r\n                            v = gamepad.xbox.buttons[\"b\"] ? 1 : 0;\r\n                            break;\r\n                        case \"x_button\":\r\n                            v = gamepad.xbox.buttons[\"x\"] ? 1 : 0;\r\n                            break;\r\n                        case \"y_button\":\r\n                            v = gamepad.xbox.buttons[\"y\"] ? 1 : 0;\r\n                            break;\r\n                        case \"lb_button\":\r\n                            v = gamepad.xbox.buttons[\"lb\"] ? 1 : 0;\r\n                            break;\r\n                        case \"rb_button\":\r\n                            v = gamepad.xbox.buttons[\"rb\"] ? 1 : 0;\r\n                            break;\r\n                        case \"ls_button\":\r\n                            v = gamepad.xbox.buttons[\"ls\"] ? 1 : 0;\r\n                            break;\r\n                        case \"rs_button\":\r\n                            v = gamepad.xbox.buttons[\"rs\"] ? 1 : 0;\r\n                            break;\r\n                        case \"hat_left\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.LEFT;\r\n                            break;\r\n                        case \"hat_right\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.RIGHT;\r\n                            break;\r\n                        case \"hat_up\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.UP;\r\n                            break;\r\n                        case \"hat_down\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.DOWN;\r\n                            break;\r\n                        case \"hat\":\r\n                            v = gamepad.xbox.hatmap;\r\n                            break;\r\n                        case \"start_button\":\r\n                            v = gamepad.xbox.buttons[\"start\"] ? 1 : 0;\r\n                            break;\r\n                        case \"back_button\":\r\n                            v = gamepad.xbox.buttons[\"back\"] ? 1 : 0;\r\n                            break;\r\n                        case \"button_pressed\":\r\n                            for (\r\n                                var j = 0;\r\n                                j < this._current_buttons.length;\r\n                                ++j\r\n                            ) {\r\n                                if (\r\n                                    this._current_buttons[j] &&\r\n                                    !this._previous_buttons[j]\r\n                                ) {\r\n                                    this.triggerSlot(\r\n                                        i,\r\n                                        GamepadInput.buttons[j]\r\n                                    );\r\n                                }\r\n                            }\r\n                            break;\r\n                        default:\r\n                            break;\r\n                    }\r\n                } else {\r\n                    //if no gamepad is connected, output 0\r\n                    switch (output.name) {\r\n                        case \"button_pressed\":\r\n                            break;\r\n                        case \"left_axis\":\r\n                        case \"right_axis\":\r\n                            v = GamepadInput.zero;\r\n                            break;\r\n                        default:\r\n                            v = 0;\r\n                    }\r\n                }\r\n                this.setOutputData(i, v);\r\n            }\r\n        }\r\n    };\r\n\r\n\tGamepadInput.mapping = {a:0,b:1,x:2,y:3,lb:4,rb:5,lt:6,rt:7,back:8,start:9,ls:10,rs:11 };\r\n\tGamepadInput.mapping_array = [\"a\",\"b\",\"x\",\"y\",\"lb\",\"rb\",\"lt\",\"rt\",\"back\",\"start\",\"ls\",\"rs\"];\r\n\r\n    GamepadInput.prototype.getGamepad = function() {\r\n        var getGamepads =\r\n            navigator.getGamepads ||\r\n            navigator.webkitGetGamepads ||\r\n            navigator.mozGetGamepads;\r\n        if (!getGamepads) {\r\n            return null;\r\n        }\r\n        var gamepads = getGamepads.call(navigator);\r\n        var gamepad = null;\r\n\r\n        this._previous_buttons.set(this._current_buttons);\r\n\r\n        //pick the first connected\r\n        for (var i = this.properties.gamepad_index; i < 4; i++) {\r\n            if (!gamepads[i]) {\r\n                continue;\r\n            }\r\n            gamepad = gamepads[i];\r\n\r\n            //xbox controller mapping\r\n            var xbox = this.xbox_mapping;\r\n            if (!xbox) {\r\n                xbox = this.xbox_mapping = {\r\n                    axes: [],\r\n                    buttons: {},\r\n                    hat: \"\",\r\n                    hatmap: GamepadInput.CENTER\r\n                };\r\n            }\r\n\r\n            xbox.axes[\"lx\"] = gamepad.axes[0];\r\n            xbox.axes[\"ly\"] = gamepad.axes[1];\r\n            xbox.axes[\"rx\"] = gamepad.axes[2];\r\n            xbox.axes[\"ry\"] = gamepad.axes[3];\r\n            xbox.axes[\"ltrigger\"] = gamepad.buttons[6].value;\r\n            xbox.axes[\"rtrigger\"] = gamepad.buttons[7].value;\r\n            xbox.hat = \"\";\r\n            xbox.hatmap = GamepadInput.CENTER;\r\n\r\n            for (var j = 0; j < gamepad.buttons.length; j++) {\r\n                this._current_buttons[j] = gamepad.buttons[j].pressed;\r\n\r\n\t\t\t\tif(j < 12)\r\n\t\t\t\t{\r\n\t\t\t\t\txbox.buttons[ GamepadInput.mapping_array[j] ] = gamepad.buttons[j].pressed;\r\n\t\t\t\t\tif(gamepad.buttons[j].was_pressed)\r\n\t\t\t\t\t\tthis.trigger( GamepadInput.mapping_array[j] + \"_button_event\" );\r\n\t\t\t\t}\r\n\t\t\t\telse //mapping of XBOX\r\n\t\t\t\t\tswitch ( j ) //I use a switch to ensure that a player with another gamepad could play\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcase 12:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"up\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.UP;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 13:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"down\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.DOWN;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 14:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"left\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.LEFT;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 15:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"right\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.RIGHT;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 16:\r\n\t\t\t\t\t\t\txbox.buttons[\"home\"] = gamepad.buttons[j].pressed;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tdefault:\r\n\t\t\t\t\t}\r\n            }\r\n            gamepad.xbox = xbox;\r\n            return gamepad;\r\n        }\r\n    };\r\n\r\n    GamepadInput.prototype.onDrawBackground = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        //render gamepad state?\r\n        var la = this._left_axis;\r\n        var ra = this._right_axis;\r\n        ctx.strokeStyle = \"#88A\";\r\n        ctx.strokeRect(\r\n            (la[0] + 1) * 0.5 * this.size[0] - 4,\r\n            (la[1] + 1) * 0.5 * this.size[1] - 4,\r\n            8,\r\n            8\r\n        );\r\n        ctx.strokeStyle = \"#8A8\";\r\n        ctx.strokeRect(\r\n            (ra[0] + 1) * 0.5 * this.size[0] - 4,\r\n            (ra[1] + 1) * 0.5 * this.size[1] - 4,\r\n            8,\r\n            8\r\n        );\r\n        var h = this.size[1] / this._current_buttons.length;\r\n        ctx.fillStyle = \"#AEB\";\r\n        for (var i = 0; i < this._current_buttons.length; ++i) {\r\n            if (this._current_buttons[i]) {\r\n                ctx.fillRect(0, h * i, 6, h);\r\n            }\r\n        }\r\n    };\r\n\r\n    GamepadInput.prototype.onGetOutputs = function() {\r\n        return [\r\n            [\"left_axis\", \"vec2\"],\r\n            [\"right_axis\", \"vec2\"],\r\n            [\"left_x_axis\", \"number\"],\r\n            [\"left_y_axis\", \"number\"],\r\n            [\"right_x_axis\", \"number\"],\r\n            [\"right_y_axis\", \"number\"],\r\n            [\"trigger_left\", \"number\"],\r\n            [\"trigger_right\", \"number\"],\r\n            [\"a_button\", \"number\"],\r\n            [\"b_button\", \"number\"],\r\n            [\"x_button\", \"number\"],\r\n            [\"y_button\", \"number\"],\r\n            [\"lb_button\", \"number\"],\r\n            [\"rb_button\", \"number\"],\r\n            [\"ls_button\", \"number\"],\r\n            [\"rs_button\", \"number\"],\r\n            [\"start_button\", \"number\"],\r\n            [\"back_button\", \"number\"],\r\n            [\"a_button_event\", LiteGraph.EVENT ],\r\n            [\"b_button_event\", LiteGraph.EVENT ],\r\n            [\"x_button_event\", LiteGraph.EVENT ],\r\n            [\"y_button_event\", LiteGraph.EVENT ],\r\n            [\"lb_button_event\", LiteGraph.EVENT ],\r\n            [\"rb_button_event\", LiteGraph.EVENT ],\r\n            [\"ls_button_event\", LiteGraph.EVENT ],\r\n            [\"rs_button_event\", LiteGraph.EVENT ],\r\n            [\"start_button_event\", LiteGraph.EVENT ],\r\n            [\"back_button_event\", LiteGraph.EVENT ],\r\n            [\"hat_left\", \"number\"],\r\n            [\"hat_right\", \"number\"],\r\n            [\"hat_up\", \"number\"],\r\n            [\"hat_down\", \"number\"],\r\n            [\"hat\", \"number\"],\r\n            [\"button_pressed\", LiteGraph.EVENT]\r\n        ];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"input/gamepad\", GamepadInput);\r\n\r\n})(this);\r\n\n(function(global) {\n    var LiteGraph = global.LiteGraph;\n\n    //Converter\n    function Converter() {\n        this.addInput(\"in\", 0);\n\t\tthis.addOutput(\"out\", 0);\n        this.size = [80, 30];\n    }\n\n    Converter.title = \"Converter\";\n    Converter.desc = \"type A to type B\";\n\n    Converter.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        if (this.outputs) {\n            for (var i = 0; i < this.outputs.length; i++) {\n                var output = this.outputs[i];\n                if (!output.links || !output.links.length) {\n                    continue;\n                }\n\n                var result = null;\n                switch (output.name) {\n                    case \"number\":\n                        result = v.length ? v[0] : parseFloat(v);\n                        break;\n                    case \"vec2\":\n                    case \"vec3\":\n                    case \"vec4\":\n                        var result = null;\n                        var count = 1;\n                        switch (output.name) {\n                            case \"vec2\":\n                                count = 2;\n                                break;\n                            case \"vec3\":\n                                count = 3;\n                                break;\n                            case \"vec4\":\n                                count = 4;\n                                break;\n                        }\n\n                        var result = new Float32Array(count);\n                        if (v.length) {\n                            for (\n                                var j = 0;\n                                j < v.length && j < result.length;\n                                j++\n                            ) {\n                                result[j] = v[j];\n                            }\n                        } else {\n                            result[0] = parseFloat(v);\n                        }\n                        break;\n                }\n                this.setOutputData(i, result);\n            }\n        }\n    };\n\n    Converter.prototype.onGetOutputs = function() {\n        return [\n            [\"number\", \"number\"],\n            [\"vec2\", \"vec2\"],\n            [\"vec3\", \"vec3\"],\n            [\"vec4\", \"vec4\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/converter\", Converter);\n\n    //Bypass\n    function Bypass() {\n        this.addInput(\"in\");\n        this.addOutput(\"out\");\n        this.size = [80, 30];\n    }\n\n    Bypass.title = \"Bypass\";\n    Bypass.desc = \"removes the type\";\n\n    Bypass.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        this.setOutputData(0, v);\n    };\n\n    LiteGraph.registerNodeType(\"math/bypass\", Bypass);\n\n    function ToNumber() {\n        this.addInput(\"in\");\n        this.addOutput(\"out\");\n    }\n\n    ToNumber.title = \"to Number\";\n    ToNumber.desc = \"Cast to number\";\n\n    ToNumber.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        this.setOutputData(0, Number(v));\n    };\n\n    LiteGraph.registerNodeType(\"math/to_number\", ToNumber);\n\n    function MathRange() {\n        this.addInput(\"in\", \"number\", { locked: true });\n        this.addOutput(\"out\", \"number\", { locked: true });\n        this.addOutput(\"clamped\", \"number\", { locked: true });\n\n        this.addProperty(\"in\", 0);\n        this.addProperty(\"in_min\", 0);\n        this.addProperty(\"in_max\", 1);\n        this.addProperty(\"out_min\", 0);\n        this.addProperty(\"out_max\", 1);\n\n        this.size = [120, 50];\n    }\n\n    MathRange.title = \"Range\";\n    MathRange.desc = \"Convert a number from one range to another\";\n\n    MathRange.prototype.getTitle = function() {\n        if (this.flags.collapsed) {\n            return (this._last_v || 0).toFixed(2);\n        }\n        return this.title;\n    };\n\n    MathRange.prototype.onExecute = function() {\n        if (this.inputs) {\n            for (var i = 0; i < this.inputs.length; i++) {\n                var input = this.inputs[i];\n                var v = this.getInputData(i);\n                if (v === undefined) {\n                    continue;\n                }\n                this.properties[input.name] = v;\n            }\n        }\n\n        var v = this.properties[\"in\"];\n        if (v === undefined || v === null || v.constructor !== Number) {\n            v = 0;\n        }\n\n        var in_min = this.properties.in_min;\n        var in_max = this.properties.in_max;\n        var out_min = this.properties.out_min;\n        var out_max = this.properties.out_max;\n\t\t/*\n\t\tif( in_min > in_max )\n\t\t{\n\t\t\tin_min = in_max;\n\t\t\tin_max = this.properties.in_min;\n\t\t}\n\t\tif( out_min > out_max )\n\t\t{\n\t\t\tout_min = out_max;\n\t\t\tout_max = this.properties.out_min;\n\t\t}\n\t\t*/\n\n        this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min;\n        this.setOutputData(0, this._last_v);\n        this.setOutputData(1, clamp( this._last_v, out_min, out_max ));\n    };\n\n    MathRange.prototype.onDrawBackground = function(ctx) {\n        //show the current value\n        if (this._last_v) {\n            this.outputs[0].label = this._last_v.toFixed(3);\n        } else {\n            this.outputs[0].label = \"?\";\n        }\n    };\n\n    MathRange.prototype.onGetInputs = function() {\n        return [\n            [\"in_min\", \"number\"],\n            [\"in_max\", \"number\"],\n            [\"out_min\", \"number\"],\n            [\"out_max\", \"number\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/range\", MathRange);\n\n    function MathRand() {\n        this.addOutput(\"value\", \"number\");\n        this.addProperty(\"min\", 0);\n        this.addProperty(\"max\", 1);\n        this.size = [80, 30];\n    }\n\n    MathRand.title = \"Rand\";\n    MathRand.desc = \"Random number\";\n\n    MathRand.prototype.onExecute = function() {\n        if (this.inputs) {\n            for (var i = 0; i < this.inputs.length; i++) {\n                var input = this.inputs[i];\n                var v = this.getInputData(i);\n                if (v === undefined) {\n                    continue;\n                }\n                this.properties[input.name] = v;\n            }\n        }\n\n        var min = this.properties.min;\n        var max = this.properties.max;\n        this._last_v = Math.random() * (max - min) + min;\n        this.setOutputData(0, this._last_v);\n    };\n\n    MathRand.prototype.onDrawBackground = function(ctx) {\n        //show the current value\n        this.outputs[0].label = (this._last_v || 0).toFixed(3);\n    };\n\n    MathRand.prototype.onGetInputs = function() {\n        return [[\"min\", \"number\"], [\"max\", \"number\"]];\n    };\n\n    LiteGraph.registerNodeType(\"math/rand\", MathRand);\n\n    //basic continuous noise\n    function MathNoise() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.addProperty(\"min\", 0);\n        this.addProperty(\"max\", 1);\n        this.addProperty(\"smooth\", true);\n        this.addProperty(\"seed\", 0);\n        this.addProperty(\"octaves\", 1);\n        this.addProperty(\"persistence\", 0.8);\n        this.addProperty(\"speed\", 1);\n        this.size = [90, 30];\n    }\n\n    MathNoise.title = \"Noise\";\n    MathNoise.desc = \"Random number with temporal continuity\";\n    MathNoise.data = null;\n\n    MathNoise.getValue = function(f, smooth) {\n        if (!MathNoise.data) {\n            MathNoise.data = new Float32Array(1024);\n            for (var i = 0; i < MathNoise.data.length; ++i) {\n                MathNoise.data[i] = Math.random();\n            }\n        }\n        f = f % 1024;\n        if (f < 0) {\n            f += 1024;\n        }\n        var f_min = Math.floor(f);\n        var f = f - f_min;\n        var r1 = MathNoise.data[f_min];\n        var r2 = MathNoise.data[f_min == 1023 ? 0 : f_min + 1];\n        if (smooth) {\n            f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n        }\n        return r1 * (1 - f) + r2 * f;\n    };\n\n    MathNoise.prototype.onExecute = function() {\n        var f = this.getInputData(0) || 0;\n\t\tvar iterations = this.properties.octaves || 1;\n\t\tvar r = 0;\n\t\tvar amp = 1;\n\t\tvar seed = this.properties.seed || 0;\n\t\tf += seed;\n\t\tvar speed = this.properties.speed || 1;\n\t\tvar total_amp = 0;\n\t\tfor(var i = 0; i < iterations; ++i)\n\t\t{\n\t\t\tr += MathNoise.getValue(f * (1+i) * speed, this.properties.smooth) * amp;\n\t\t\ttotal_amp += amp;\n\t\t\tamp *= this.properties.persistence;\n\t\t\tif(amp < 0.001)\n\t\t\t\tbreak;\n\t\t}\n\t\tr /= total_amp;\n        var min = this.properties.min;\n        var max = this.properties.max;\n        this._last_v = r * (max - min) + min;\n        this.setOutputData(0, this._last_v);\n    };\n\n    MathNoise.prototype.onDrawBackground = function(ctx) {\n        //show the current value\n        this.outputs[0].label = (this._last_v || 0).toFixed(3);\n    };\n\n    LiteGraph.registerNodeType(\"math/noise\", MathNoise);\n\n    //generates spikes every random time\n    function MathSpikes() {\n        this.addOutput(\"out\", \"number\");\n        this.addProperty(\"min_time\", 1);\n        this.addProperty(\"max_time\", 2);\n        this.addProperty(\"duration\", 0.2);\n        this.size = [90, 30];\n        this._remaining_time = 0;\n        this._blink_time = 0;\n    }\n\n    MathSpikes.title = \"Spikes\";\n    MathSpikes.desc = \"spike every random time\";\n\n    MathSpikes.prototype.onExecute = function() {\n        var dt = this.graph.elapsed_time; //in secs\n\n        this._remaining_time -= dt;\n        this._blink_time -= dt;\n\n        var v = 0;\n        if (this._blink_time > 0) {\n            var f = this._blink_time / this.properties.duration;\n            v = 1 / (Math.pow(f * 8 - 4, 4) + 1);\n        }\n\n        if (this._remaining_time < 0) {\n            this._remaining_time =\n                Math.random() *\n                    (this.properties.max_time - this.properties.min_time) +\n                this.properties.min_time;\n            this._blink_time = this.properties.duration;\n            this.boxcolor = \"#FFF\";\n        } else {\n            this.boxcolor = \"#000\";\n        }\n        this.setOutputData(0, v);\n    };\n\n    LiteGraph.registerNodeType(\"math/spikes\", MathSpikes);\n\n    //Math clamp\n    function MathClamp() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n        this.addProperty(\"min\", 0);\n        this.addProperty(\"max\", 1);\n    }\n\n    MathClamp.title = \"Clamp\";\n    MathClamp.desc = \"Clamp number between min and max\";\n    //MathClamp.filter = \"shader\";\n\n    MathClamp.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        v = Math.max(this.properties.min, v);\n        v = Math.min(this.properties.max, v);\n        this.setOutputData(0, v);\n    };\n\n    MathClamp.prototype.getCode = function(lang) {\n        var code = \"\";\n        if (this.isInputConnected(0)) {\n            code +=\n                \"clamp({{0}},\" +\n                this.properties.min +\n                \",\" +\n                this.properties.max +\n                \")\";\n        }\n        return code;\n    };\n\n    LiteGraph.registerNodeType(\"math/clamp\", MathClamp);\n\n    //Math ABS\n    function MathLerp() {\n        this.properties = { f: 0.5 };\n        this.addInput(\"A\", \"number\");\n        this.addInput(\"B\", \"number\");\n\n        this.addOutput(\"out\", \"number\");\n    }\n\n    MathLerp.title = \"Lerp\";\n    MathLerp.desc = \"Linear Interpolation\";\n\n    MathLerp.prototype.onExecute = function() {\n        var v1 = this.getInputData(0);\n        if (v1 == null) {\n            v1 = 0;\n        }\n        var v2 = this.getInputData(1);\n        if (v2 == null) {\n            v2 = 0;\n        }\n\n        var f = this.properties.f;\n\n        var _f = this.getInputData(2);\n        if (_f !== undefined) {\n            f = _f;\n        }\n\n        this.setOutputData(0, v1 * (1 - f) + v2 * f);\n    };\n\n    MathLerp.prototype.onGetInputs = function() {\n        return [[\"f\", \"number\"]];\n    };\n\n    LiteGraph.registerNodeType(\"math/lerp\", MathLerp);\n\n    //Math ABS\n    function MathAbs() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n    }\n\n    MathAbs.title = \"Abs\";\n    MathAbs.desc = \"Absolute\";\n\n    MathAbs.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        this.setOutputData(0, Math.abs(v));\n    };\n\n    LiteGraph.registerNodeType(\"math/abs\", MathAbs);\n\n    //Math Floor\n    function MathFloor() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n    }\n\n    MathFloor.title = \"Floor\";\n    MathFloor.desc = \"Floor number to remove fractional part\";\n\n    MathFloor.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        this.setOutputData(0, Math.floor(v));\n    };\n\n    LiteGraph.registerNodeType(\"math/floor\", MathFloor);\n\n    //Math frac\n    function MathFrac() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n    }\n\n    MathFrac.title = \"Frac\";\n    MathFrac.desc = \"Returns fractional part\";\n\n    MathFrac.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        this.setOutputData(0, v % 1);\n    };\n\n    LiteGraph.registerNodeType(\"math/frac\", MathFrac);\n\n    //Math Floor\n    function MathSmoothStep() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n        this.properties = { A: 0, B: 1 };\n    }\n\n    MathSmoothStep.title = \"Smoothstep\";\n    MathSmoothStep.desc = \"Smoothstep\";\n\n    MathSmoothStep.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v === undefined) {\n            return;\n        }\n\n        var edge0 = this.properties.A;\n        var edge1 = this.properties.B;\n\n        // Scale, bias and saturate x to 0..1 range\n        v = clamp((v - edge0) / (edge1 - edge0), 0.0, 1.0);\n        // Evaluate polynomial\n        v = v * v * (3 - 2 * v);\n\n        this.setOutputData(0, v);\n    };\n\n    LiteGraph.registerNodeType(\"math/smoothstep\", MathSmoothStep);\n\n    //Math scale\n    function MathScale() {\n        this.addInput(\"in\", \"number\", { label: \"\" });\n        this.addOutput(\"out\", \"number\", { label: \"\" });\n        this.size = [80, 30];\n        this.addProperty(\"factor\", 1);\n    }\n\n    MathScale.title = \"Scale\";\n    MathScale.desc = \"v * factor\";\n\n    MathScale.prototype.onExecute = function() {\n        var value = this.getInputData(0);\n        if (value != null) {\n            this.setOutputData(0, value * this.properties.factor);\n        }\n    };\n\n    LiteGraph.registerNodeType(\"math/scale\", MathScale);\n\n\t//Gate\n\tfunction Gate() {\n\t\tthis.addInput(\"v\",\"boolean\");\n\t\tthis.addInput(\"A\");\n\t\tthis.addInput(\"B\");\n\t\tthis.addOutput(\"out\");\n\t}\n\n\tGate.title = \"Gate\";\n\tGate.desc = \"if v is true, then outputs A, otherwise B\";\n\n\tGate.prototype.onExecute = function() {\n\t\tvar v = this.getInputData(0);\n\t\tthis.setOutputData(0, this.getInputData( v ? 1 : 2 ));\n\t};\n\n\tLiteGraph.registerNodeType(\"math/gate\", Gate);\n\n\n    //Math Average\n    function MathAverageFilter() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n        this.addProperty(\"samples\", 10);\n        this._values = new Float32Array(10);\n        this._current = 0;\n    }\n\n    MathAverageFilter.title = \"Average\";\n    MathAverageFilter.desc = \"Average Filter\";\n\n    MathAverageFilter.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            v = 0;\n        }\n\n        var num_samples = this._values.length;\n\n        this._values[this._current % num_samples] = v;\n        this._current += 1;\n        if (this._current > num_samples) {\n            this._current = 0;\n        }\n\n        var avr = 0;\n        for (var i = 0; i < num_samples; ++i) {\n            avr += this._values[i];\n        }\n\n        this.setOutputData(0, avr / num_samples);\n    };\n\n    MathAverageFilter.prototype.onPropertyChanged = function(name, value) {\n        if (value < 1) {\n            value = 1;\n        }\n        this.properties.samples = Math.round(value);\n        var old = this._values;\n\n        this._values = new Float32Array(this.properties.samples);\n        if (old.length <= this._values.length) {\n            this._values.set(old);\n        } else {\n            this._values.set(old.subarray(0, this._values.length));\n        }\n    };\n\n    LiteGraph.registerNodeType(\"math/average\", MathAverageFilter);\n\n    //Math\n    function MathTendTo() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.addProperty(\"factor\", 0.1);\n        this.size = [80, 30];\n        this._value = null;\n    }\n\n    MathTendTo.title = \"TendTo\";\n    MathTendTo.desc = \"moves the output value always closer to the input\";\n\n    MathTendTo.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            v = 0;\n        }\n        var f = this.properties.factor;\n        if (this._value == null) {\n            this._value = v;\n        } else {\n            this._value = this._value * (1 - f) + v * f;\n        }\n        this.setOutputData(0, this._value);\n    };\n\n    LiteGraph.registerNodeType(\"math/tendTo\", MathTendTo);\n\n    //Math operation\n    function MathOperation() {\n        this.addInput(\"A\", \"number,array,object\");\n        this.addInput(\"B\", \"number\");\n        this.addOutput(\"=\", \"number\");\n        this.addProperty(\"A\", 1);\n        this.addProperty(\"B\", 1);\n        this.addProperty(\"OP\", \"+\", \"enum\", { values: MathOperation.values });\n\t\tthis._func = MathOperation.funcs[this.properties.OP];\n\t\tthis._result = []; //only used for arrays\n    }\n\n    MathOperation.values = [\"+\", \"-\", \"*\", \"/\", \"%\", \"^\", \"max\", \"min\"];\n    MathOperation.funcs = {\n        \"+\": function(A,B) { return A + B; },\n        \"-\": function(A,B) { return A - B; },\n        \"x\": function(A,B) { return A * B; },\n        \"X\": function(A,B) { return A * B; },\n        \"*\": function(A,B) { return A * B; },\n        \"/\": function(A,B) { return A / B; },\n        \"%\": function(A,B) { return A % B; },\n        \"^\": function(A,B) { return Math.pow(A, B); },\n        \"max\": function(A,B) { return Math.max(A, B); },\n        \"min\": function(A,B) { return Math.min(A, B); }\n    };\n\n\tMathOperation.title = \"Operation\";\n    MathOperation.desc = \"Easy math operators\";\n    MathOperation[\"@OP\"] = {\n        type: \"enum\",\n        title: \"operation\",\n        values: MathOperation.values\n    };\n    MathOperation.size = [100, 60];\n\n    MathOperation.prototype.getTitle = function() {\n\t\tif(this.properties.OP == \"max\" || this.properties.OP == \"min\")\n\t\t\treturn this.properties.OP + \"(A,B)\";\n        return \"A \" + this.properties.OP + \" B\";\n    };\n\n    MathOperation.prototype.setValue = function(v) {\n        if (typeof v == \"string\") {\n            v = parseFloat(v);\n        }\n        this.properties[\"value\"] = v;\n    };\n\n    MathOperation.prototype.onPropertyChanged = function(name, value)\n\t{\n\t\tif (name != \"OP\")\n\t\t\treturn;\n        this._func = MathOperation.funcs[this.properties.OP];\n        if(!this._func)\n        {\n            console.warn(\"Unknown operation: \" + this.properties.OP);\n            this._func = function(A) { return A; };\n        }\n\t}\n\n    MathOperation.prototype.onExecute = function() {\n        var A = this.getInputData(0);\n        var B = this.getInputData(1);\n        if ( A != null ) {\n\t\t\tif( A.constructor === Number )\n\t            this.properties[\"A\"] = A;\n        } else {\n            A = this.properties[\"A\"];\n        }\n\n        if (B != null) {\n            this.properties[\"B\"] = B;\n        } else {\n            B = this.properties[\"B\"];\n        }\n\n        var func = MathOperation.funcs[this.properties.OP];\n\n\t\tvar result;\n\t\tif(A.constructor === Number)\n\t\t{\n\t        result = 0;\n\t\t\tresult = func(A,B);\n\t\t}\n\t\telse if(A.constructor === Array)\n\t\t{\n\t\t\tresult = this._result;\n\t\t\tresult.length = A.length;\n\t\t\tfor(var i = 0; i < A.length; ++i)\n\t\t\t\tresult[i] = func(A[i],B);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tresult = {};\n\t\t\tfor(var i in A)\n\t\t\t\tresult[i] = func(A[i],B);\n\t\t}\n\t    this.setOutputData(0, result);\n    };\n\n    MathOperation.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        ctx.font = \"40px Arial\";\n        ctx.fillStyle = \"#666\";\n        ctx.textAlign = \"center\";\n        ctx.fillText(\n            this.properties.OP,\n            this.size[0] * 0.5,\n            (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5\n        );\n        ctx.textAlign = \"left\";\n    };\n\n    LiteGraph.registerNodeType(\"math/operation\", MathOperation);\n\n    LiteGraph.registerSearchboxExtra(\"math/operation\", \"MAX\", {\n        properties: {OP:\"max\"},\n        title: \"MAX()\"\n    });\n\n    LiteGraph.registerSearchboxExtra(\"math/operation\", \"MIN\", {\n        properties: {OP:\"min\"},\n        title: \"MIN()\"\n    });\n\n\n    //Math compare\n    function MathCompare() {\n        this.addInput(\"A\", \"number\");\n        this.addInput(\"B\", \"number\");\n        this.addOutput(\"A==B\", \"boolean\");\n        this.addOutput(\"A!=B\", \"boolean\");\n        this.addProperty(\"A\", 0);\n        this.addProperty(\"B\", 0);\n    }\n\n    MathCompare.title = \"Compare\";\n    MathCompare.desc = \"compares between two values\";\n\n    MathCompare.prototype.onExecute = function() {\n        var A = this.getInputData(0);\n        var B = this.getInputData(1);\n        if (A !== undefined) {\n            this.properties[\"A\"] = A;\n        } else {\n            A = this.properties[\"A\"];\n        }\n\n        if (B !== undefined) {\n            this.properties[\"B\"] = B;\n        } else {\n            B = this.properties[\"B\"];\n        }\n\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            var output = this.outputs[i];\n            if (!output.links || !output.links.length) {\n                continue;\n            }\n            var value;\n            switch (output.name) {\n                case \"A==B\":\n                    value = A == B;\n                    break;\n                case \"A!=B\":\n                    value = A != B;\n                    break;\n                case \"A>B\":\n                    value = A > B;\n                    break;\n                case \"A<B\":\n                    value = A < B;\n                    break;\n                case \"A<=B\":\n                    value = A <= B;\n                    break;\n                case \"A>=B\":\n                    value = A >= B;\n                    break;\n            }\n            this.setOutputData(i, value);\n        }\n    };\n\n    MathCompare.prototype.onGetOutputs = function() {\n        return [\n            [\"A==B\", \"boolean\"],\n            [\"A!=B\", \"boolean\"],\n            [\"A>B\", \"boolean\"],\n            [\"A<B\", \"boolean\"],\n            [\"A>=B\", \"boolean\"],\n            [\"A<=B\", \"boolean\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/compare\", MathCompare);\n\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"==\", {\n        outputs: [[\"A==B\", \"boolean\"]],\n        title: \"A==B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"!=\", {\n        outputs: [[\"A!=B\", \"boolean\"]],\n        title: \"A!=B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \">\", {\n        outputs: [[\"A>B\", \"boolean\"]],\n        title: \"A>B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"<\", {\n        outputs: [[\"A<B\", \"boolean\"]],\n        title: \"A<B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \">=\", {\n        outputs: [[\"A>=B\", \"boolean\"]],\n        title: \"A>=B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"<=\", {\n        outputs: [[\"A<=B\", \"boolean\"]],\n        title: \"A<=B\"\n    });\n\n    function MathCondition() {\n        this.addInput(\"A\", \"number\");\n        this.addInput(\"B\", \"number\");\n        this.addOutput(\"true\", \"boolean\");\n        this.addOutput(\"false\", \"boolean\");\n        this.addProperty(\"A\", 1);\n        this.addProperty(\"B\", 1);\n        this.addProperty(\"OP\", \">\", \"enum\", { values: MathCondition.values });\n\t\tthis.addWidget(\"combo\",\"Cond.\",this.properties.OP,{ property: \"OP\", values: MathCondition.values } );\n\n        this.size = [80, 60];\n    }\n\n    MathCondition.values = [\">\", \"<\", \"==\", \"!=\", \"<=\", \">=\", \"||\", \"&&\" ];\n    MathCondition[\"@OP\"] = {\n        type: \"enum\",\n        title: \"operation\",\n        values: MathCondition.values\n    };\n\n    MathCondition.title = \"Condition\";\n    MathCondition.desc = \"evaluates condition between A and B\";\n\n    MathCondition.prototype.getTitle = function() {\n        return \"A \" + this.properties.OP + \" B\";\n    };\n\n    MathCondition.prototype.onExecute = function() {\n        var A = this.getInputData(0);\n        if (A === undefined) {\n            A = this.properties.A;\n        } else {\n            this.properties.A = A;\n        }\n\n        var B = this.getInputData(1);\n        if (B === undefined) {\n            B = this.properties.B;\n        } else {\n            this.properties.B = B;\n        }\n\n        var result = true;\n        switch (this.properties.OP) {\n            case \">\":\n                result = A > B;\n                break;\n            case \"<\":\n                result = A < B;\n                break;\n            case \"==\":\n                result = A == B;\n                break;\n            case \"!=\":\n                result = A != B;\n                break;\n            case \"<=\":\n                result = A <= B;\n                break;\n            case \">=\":\n                result = A >= B;\n                break;\n            case \"||\":\n                result = A || B;\n                break;\n            case \"&&\":\n                result = A && B;\n                break;\n        }\n\n        this.setOutputData(0, result);\n        this.setOutputData(1, !result);\n    };\n\n    LiteGraph.registerNodeType(\"math/condition\", MathCondition);\n\n\n    function MathBranch() {\n        this.addInput(\"in\", 0);\n        this.addInput(\"cond\", \"boolean\");\n        this.addOutput(\"true\", 0);\n        this.addOutput(\"false\", 0);\n        this.size = [80, 60];\n    }\n\n    MathBranch.title = \"Branch\";\n    MathBranch.desc = \"If condition is true, outputs IN in true, otherwise in false\";\n\n    MathBranch.prototype.onExecute = function() {\n        var V = this.getInputData(0);\n        var cond = this.getInputData(1);\n\n\t\tif(cond)\n\t\t{\n\t\t\tthis.setOutputData(0, V);\n\t\t\tthis.setOutputData(1, null);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis.setOutputData(0, null);\n\t\t\tthis.setOutputData(1, V);\n\t\t}\n\t}\n\n    LiteGraph.registerNodeType(\"math/branch\", MathBranch);\n\n\n    function MathAccumulate() {\n        this.addInput(\"inc\", \"number\");\n        this.addOutput(\"total\", \"number\");\n        this.addProperty(\"increment\", 1);\n        this.addProperty(\"value\", 0);\n    }\n\n    MathAccumulate.title = \"Accumulate\";\n    MathAccumulate.desc = \"Increments a value every time\";\n\n    MathAccumulate.prototype.onExecute = function() {\n        if (this.properties.value === null) {\n            this.properties.value = 0;\n        }\n\n        var inc = this.getInputData(0);\n        if (inc !== null) {\n            this.properties.value += inc;\n        } else {\n            this.properties.value += this.properties.increment;\n        }\n        this.setOutputData(0, this.properties.value);\n    };\n\n    LiteGraph.registerNodeType(\"math/accumulate\", MathAccumulate);\n\n    //Math Trigonometry\n    function MathTrigonometry() {\n        this.addInput(\"v\", \"number\");\n        this.addOutput(\"sin\", \"number\");\n\n        this.addProperty(\"amplitude\", 1);\n        this.addProperty(\"offset\", 0);\n        this.bgImageUrl = \"nodes/imgs/icon-sin.png\";\n    }\n\n    MathTrigonometry.title = \"Trigonometry\";\n    MathTrigonometry.desc = \"Sin Cos Tan\";\n    //MathTrigonometry.filter = \"shader\";\n\n    MathTrigonometry.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            v = 0;\n        }\n        var amplitude = this.properties[\"amplitude\"];\n        var slot = this.findInputSlot(\"amplitude\");\n        if (slot != -1) {\n            amplitude = this.getInputData(slot);\n        }\n        var offset = this.properties[\"offset\"];\n        slot = this.findInputSlot(\"offset\");\n        if (slot != -1) {\n            offset = this.getInputData(slot);\n        }\n\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            var output = this.outputs[i];\n            var value;\n            switch (output.name) {\n                case \"sin\":\n                    value = Math.sin(v);\n                    break;\n                case \"cos\":\n                    value = Math.cos(v);\n                    break;\n                case \"tan\":\n                    value = Math.tan(v);\n                    break;\n                case \"asin\":\n                    value = Math.asin(v);\n                    break;\n                case \"acos\":\n                    value = Math.acos(v);\n                    break;\n                case \"atan\":\n                    value = Math.atan(v);\n                    break;\n            }\n            this.setOutputData(i, amplitude * value + offset);\n        }\n    };\n\n    MathTrigonometry.prototype.onGetInputs = function() {\n        return [[\"v\", \"number\"], [\"amplitude\", \"number\"], [\"offset\", \"number\"]];\n    };\n\n    MathTrigonometry.prototype.onGetOutputs = function() {\n        return [\n            [\"sin\", \"number\"],\n            [\"cos\", \"number\"],\n            [\"tan\", \"number\"],\n            [\"asin\", \"number\"],\n            [\"acos\", \"number\"],\n            [\"atan\", \"number\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/trigonometry\", MathTrigonometry);\n\n    LiteGraph.registerSearchboxExtra(\"math/trigonometry\", \"SIN()\", {\n        outputs: [[\"sin\", \"number\"]],\n        title: \"SIN()\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/trigonometry\", \"COS()\", {\n        outputs: [[\"cos\", \"number\"]],\n        title: \"COS()\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/trigonometry\", \"TAN()\", {\n        outputs: [[\"tan\", \"number\"]],\n        title: \"TAN()\"\n    });\n\n    //math library for safe math operations without eval\n    function MathFormula() {\n        this.addInput(\"x\", \"number\");\n        this.addInput(\"y\", \"number\");\n        this.addOutput(\"\", \"number\");\n        this.properties = { x: 1.0, y: 1.0, formula: \"x+y\" };\n        this.code_widget = this.addWidget(\n            \"text\",\n            \"F(x,y)\",\n            this.properties.formula,\n            function(v, canvas, node) {\n                node.properties.formula = v;\n            }\n        );\n        this.addWidget(\"toggle\", \"allow\", LiteGraph.allow_scripts, function(v) {\n            LiteGraph.allow_scripts = v;\n        });\n        this._func = null;\n    }\n\n    MathFormula.title = \"Formula\";\n    MathFormula.desc = \"Compute formula\";\n    MathFormula.size = [160, 100];\n\n    MathAverageFilter.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"formula\") {\n            this.code_widget.value = value;\n        }\n    };\n\n    MathFormula.prototype.onExecute = function() {\n        if (!LiteGraph.allow_scripts) {\n            return;\n        }\n\n        var x = this.getInputData(0);\n        var y = this.getInputData(1);\n        if (x != null) {\n            this.properties[\"x\"] = x;\n        } else {\n            x = this.properties[\"x\"];\n        }\n\n        if (y != null) {\n            this.properties[\"y\"] = y;\n        } else {\n            y = this.properties[\"y\"];\n        }\n\n        var f = this.properties[\"formula\"];\n\n        var value;\n        try {\n            if (!this._func || this._func_code != this.properties.formula) {\n                this._func = new Function(\n                    \"x\",\n                    \"y\",\n                    \"TIME\",\n                    \"return \" + this.properties.formula\n                );\n                this._func_code = this.properties.formula;\n            }\n            value = this._func(x, y, this.graph.globaltime);\n            this.boxcolor = null;\n        } catch (err) {\n            this.boxcolor = \"red\";\n        }\n        this.setOutputData(0, value);\n    };\n\n    MathFormula.prototype.getTitle = function() {\n        return this._func_code || \"Formula\";\n    };\n\n    MathFormula.prototype.onDrawBackground = function() {\n        var f = this.properties[\"formula\"];\n        if (this.outputs && this.outputs.length) {\n            this.outputs[0].label = f;\n        }\n    };\n\n    LiteGraph.registerNodeType(\"math/formula\", MathFormula);\n\n    function Math3DVec2ToXY() {\n        this.addInput(\"vec2\", \"vec2\");\n        this.addOutput(\"x\", \"number\");\n        this.addOutput(\"y\", \"number\");\n    }\n\n    Math3DVec2ToXY.title = \"Vec2->XY\";\n    Math3DVec2ToXY.desc = \"vector 2 to components\";\n\n    Math3DVec2ToXY.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        this.setOutputData(0, v[0]);\n        this.setOutputData(1, v[1]);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/vec2-to-xy\", Math3DVec2ToXY);\n\n    function Math3DXYToVec2() {\n        this.addInputs([[\"x\", \"number\"], [\"y\", \"number\"]]);\n        this.addOutput(\"vec2\", \"vec2\");\n        this.properties = { x: 0, y: 0 };\n        this._data = new Float32Array(2);\n    }\n\n    Math3DXYToVec2.title = \"XY->Vec2\";\n    Math3DXYToVec2.desc = \"components to vector2\";\n\n    Math3DXYToVec2.prototype.onExecute = function() {\n        var x = this.getInputData(0);\n        if (x == null) {\n            x = this.properties.x;\n        }\n        var y = this.getInputData(1);\n        if (y == null) {\n            y = this.properties.y;\n        }\n\n        var data = this._data;\n        data[0] = x;\n        data[1] = y;\n\n        this.setOutputData(0, data);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/xy-to-vec2\", Math3DXYToVec2);\n\n    function Math3DVec3ToXYZ() {\n        this.addInput(\"vec3\", \"vec3\");\n        this.addOutput(\"x\", \"number\");\n        this.addOutput(\"y\", \"number\");\n        this.addOutput(\"z\", \"number\");\n    }\n\n    Math3DVec3ToXYZ.title = \"Vec3->XYZ\";\n    Math3DVec3ToXYZ.desc = \"vector 3 to components\";\n\n    Math3DVec3ToXYZ.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        this.setOutputData(0, v[0]);\n        this.setOutputData(1, v[1]);\n        this.setOutputData(2, v[2]);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/vec3-to-xyz\", Math3DVec3ToXYZ);\n\n    function Math3DXYZToVec3() {\n        this.addInputs([[\"x\", \"number\"], [\"y\", \"number\"], [\"z\", \"number\"]]);\n        this.addOutput(\"vec3\", \"vec3\");\n        this.properties = { x: 0, y: 0, z: 0 };\n        this._data = new Float32Array(3);\n    }\n\n    Math3DXYZToVec3.title = \"XYZ->Vec3\";\n    Math3DXYZToVec3.desc = \"components to vector3\";\n\n    Math3DXYZToVec3.prototype.onExecute = function() {\n        var x = this.getInputData(0);\n        if (x == null) {\n            x = this.properties.x;\n        }\n        var y = this.getInputData(1);\n        if (y == null) {\n            y = this.properties.y;\n        }\n        var z = this.getInputData(2);\n        if (z == null) {\n            z = this.properties.z;\n        }\n\n        var data = this._data;\n        data[0] = x;\n        data[1] = y;\n        data[2] = z;\n\n        this.setOutputData(0, data);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/xyz-to-vec3\", Math3DXYZToVec3);\n\n    function Math3DVec4ToXYZW() {\n        this.addInput(\"vec4\", \"vec4\");\n        this.addOutput(\"x\", \"number\");\n        this.addOutput(\"y\", \"number\");\n        this.addOutput(\"z\", \"number\");\n        this.addOutput(\"w\", \"number\");\n    }\n\n    Math3DVec4ToXYZW.title = \"Vec4->XYZW\";\n    Math3DVec4ToXYZW.desc = \"vector 4 to components\";\n\n    Math3DVec4ToXYZW.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        this.setOutputData(0, v[0]);\n        this.setOutputData(1, v[1]);\n        this.setOutputData(2, v[2]);\n        this.setOutputData(3, v[3]);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/vec4-to-xyzw\", Math3DVec4ToXYZW);\n\n    function Math3DXYZWToVec4() {\n        this.addInputs([\n            [\"x\", \"number\"],\n            [\"y\", \"number\"],\n            [\"z\", \"number\"],\n            [\"w\", \"number\"]\n        ]);\n        this.addOutput(\"vec4\", \"vec4\");\n        this.properties = { x: 0, y: 0, z: 0, w: 0 };\n        this._data = new Float32Array(4);\n    }\n\n    Math3DXYZWToVec4.title = \"XYZW->Vec4\";\n    Math3DXYZWToVec4.desc = \"components to vector4\";\n\n    Math3DXYZWToVec4.prototype.onExecute = function() {\n        var x = this.getInputData(0);\n        if (x == null) {\n            x = this.properties.x;\n        }\n        var y = this.getInputData(1);\n        if (y == null) {\n            y = this.properties.y;\n        }\n        var z = this.getInputData(2);\n        if (z == null) {\n            z = this.properties.z;\n        }\n        var w = this.getInputData(3);\n        if (w == null) {\n            w = this.properties.w;\n        }\n\n        var data = this._data;\n        data[0] = x;\n        data[1] = y;\n        data[2] = z;\n        data[3] = w;\n\n        this.setOutputData(0, data);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/xyzw-to-vec4\", Math3DXYZWToVec4);\n\n})(this);\n\n//basic nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    function toString(a) {\r\n\t\tif(a && a.constructor === Object)\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\treturn JSON.stringify(a);\r\n\t\t\t}\r\n\t\t\tcatch (err)\r\n\t\t\t{\r\n\t\t\t\treturn String(a);\r\n\t\t\t}\r\n\t\t}\r\n        return String(a);\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\"string/toString\", toString, [\"\"], \"string\");\r\n\r\n    function compare(a, b) {\r\n        return a == b;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/compare\",\r\n        compare,\r\n        [\"string\", \"string\"],\r\n        \"boolean\"\r\n    );\r\n\r\n    function concatenate(a, b) {\r\n        if (a === undefined) {\r\n            return b;\r\n        }\r\n        if (b === undefined) {\r\n            return a;\r\n        }\r\n        return a + b;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/concatenate\",\r\n        concatenate,\r\n        [\"string\", \"string\"],\r\n        \"string\"\r\n    );\r\n\r\n    function contains(a, b) {\r\n        if (a === undefined || b === undefined) {\r\n            return false;\r\n        }\r\n        return a.indexOf(b) != -1;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/contains\",\r\n        contains,\r\n        [\"string\", \"string\"],\r\n        \"boolean\"\r\n    );\r\n\r\n    function toUpperCase(a) {\r\n        if (a != null && a.constructor === String) {\r\n            return a.toUpperCase();\r\n        }\r\n        return a;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/toUpperCase\",\r\n        toUpperCase,\r\n        [\"string\"],\r\n        \"string\"\r\n    );\r\n\r\n    function split(str, separator) {\r\n\t\tif(separator == null)\r\n\t\t\tseparator = this.properties.separator;\r\n        if (str == null )\r\n\t        return [];\r\n\t\tif( str.constructor === String )\r\n\t\t\treturn str.split(separator || \" \");\r\n\t\telse if( str.constructor === Array )\r\n\t\t{\r\n\t\t\tvar r = [];\r\n\t\t\tfor(var i = 0; i < str.length; ++i){\r\n                if (typeof str[i] == \"string\")\r\n\t\t\t\t    r[i] = str[i].split(separator || \" \");\r\n            }\r\n\t\t\treturn r;\r\n\t\t}\r\n        return null;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/split\",\r\n        split,\r\n        [\"string,array\", \"string\"],\r\n        \"array\",\r\n\t\t{ separator: \",\" }\r\n    );\r\n\r\n    function toFixed(a) {\r\n        if (a != null && a.constructor === Number) {\r\n            return a.toFixed(this.properties.precision);\r\n        }\r\n        return a;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/toFixed\",\r\n        toFixed,\r\n        [\"number\"],\r\n        \"string\",\r\n        { precision: 0 }\r\n    );\r\n\r\n\r\n    function StringToTable() {\r\n        this.addInput(\"\", \"string\");\r\n        this.addOutput(\"table\", \"table\");\r\n        this.addOutput(\"rows\", \"number\");\r\n        this.addProperty(\"value\", \"\");\r\n        this.addProperty(\"separator\", \",\");\r\n\t\tthis._table = null;\r\n    }\r\n\r\n    StringToTable.title = \"toTable\";\r\n    StringToTable.desc = \"Splits a string to table\";\r\n\r\n    StringToTable.prototype.onExecute = function() {\r\n        var input = this.getInputData(0);\r\n\t\tif(!input)\r\n\t\t\treturn;\r\n\t\tvar separator = this.properties.separator || \",\";\r\n\t\tif(input != this._str || separator != this._last_separator )\r\n\t\t{\r\n\t\t\tthis._last_separator = separator;\r\n\t\t\tthis._str = input;\r\n\t\t\tthis._table = input.split(\"\\n\").map(function(a){ return a.trim().split(separator)});\r\n\t\t}\r\n        this.setOutputData(0, this._table );\r\n        this.setOutputData(1, this._table ? this._table.length : 0 );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"string/toTable\", StringToTable);\r\n\r\n})(this);\r\n\n(function(global) {\n    var LiteGraph = global.LiteGraph;\n\n    function Selector() {\n        this.addInput(\"sel\", \"number\");\n        this.addInput(\"A\");\n        this.addInput(\"B\");\n        this.addInput(\"C\");\n        this.addInput(\"D\");\n        this.addOutput(\"out\");\n\n        this.selected = 0;\n    }\n\n    Selector.title = \"Selector\";\n    Selector.desc = \"selects an output\";\n\n    Selector.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n        ctx.fillStyle = \"#AFB\";\n        var y = (this.selected + 1) * LiteGraph.NODE_SLOT_HEIGHT + 6;\n        ctx.beginPath();\n        ctx.moveTo(50, y);\n        ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT);\n        ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5);\n        ctx.fill();\n    };\n\n    Selector.prototype.onExecute = function() {\n        var sel = this.getInputData(0);\n        if (sel == null || sel.constructor !== Number)\n            sel = 0;\n        this.selected = sel = Math.round(sel) % (this.inputs.length - 1);\n        var v = this.getInputData(sel + 1);\n        if (v !== undefined) {\n            this.setOutputData(0, v);\n        }\n    };\n\n    Selector.prototype.onGetInputs = function() {\n        return [[\"E\", 0], [\"F\", 0], [\"G\", 0], [\"H\", 0]];\n    };\n\n    LiteGraph.registerNodeType(\"logic/selector\", Selector);\n\n    function Sequence() {\n        this.properties = {\n            sequence: \"A,B,C\"\n        };\n        this.addInput(\"index\", \"number\");\n        this.addInput(\"seq\");\n        this.addOutput(\"out\");\n\n        this.index = 0;\n        this.values = this.properties.sequence.split(\",\");\n    }\n\n    Sequence.title = \"Sequence\";\n    Sequence.desc = \"select one element from a sequence from a string\";\n\n    Sequence.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"sequence\") {\n            this.values = value.split(\",\");\n        }\n    };\n\n    Sequence.prototype.onExecute = function() {\n        var seq = this.getInputData(1);\n        if (seq && seq != this.current_sequence) {\n            this.values = seq.split(\",\");\n            this.current_sequence = seq;\n        }\n        var index = this.getInputData(0);\n        if (index == null) {\n            index = 0;\n        }\n        this.index = index = Math.round(index) % this.values.length;\n\n        this.setOutputData(0, this.values[index]);\n    };\n\n    LiteGraph.registerNodeType(\"logic/sequence\", Sequence);\n\t\n    \n    function logicAnd(){\n        this.properties = { };\n        this.addInput(\"a\", \"boolean\");\n        this.addInput(\"b\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicAnd.title = \"AND\";\n    logicAnd.desc = \"Return true if all inputs are true\";\n    logicAnd.prototype.onExecute = function() {\n        var ret = true;\n        for (var inX in this.inputs){\n            if (!this.getInputData(inX)){\n                var ret = false;\n                break;\n            }\n        }\n        this.setOutputData(0, ret);\n    };\n    logicAnd.prototype.onGetInputs = function() {\n        return [\n            [\"and\", \"boolean\"]\n        ];\n    };\n    LiteGraph.registerNodeType(\"logic/AND\", logicAnd);\n    \n    \n    function logicOr(){\n        this.properties = { };\n        this.addInput(\"a\", \"boolean\");\n        this.addInput(\"b\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicOr.title = \"OR\";\n    logicOr.desc = \"Return true if at least one input is true\";\n    logicOr.prototype.onExecute = function() {\n        var ret = false;\n        for (var inX in this.inputs){\n            if (this.getInputData(inX)){\n                ret = true;\n                break;\n            }\n        }\n        this.setOutputData(0, ret);\n    };\n    logicOr.prototype.onGetInputs = function() {\n        return [\n            [\"or\", \"boolean\"]\n        ];\n    };\n    LiteGraph.registerNodeType(\"logic/OR\", logicOr);\n    \n    \n    function logicNot(){\n        this.properties = { };\n        this.addInput(\"in\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicNot.title = \"NOT\";\n    logicNot.desc = \"Return the logical negation\";\n    logicNot.prototype.onExecute = function() {\n        var ret = !this.getInputData(0);\n        this.setOutputData(0, ret);\n    };\n    LiteGraph.registerNodeType(\"logic/NOT\", logicNot);\n    \n    \n    function logicCompare(){\n        this.properties = { };\n        this.addInput(\"a\", \"boolean\");\n        this.addInput(\"b\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicCompare.title = \"bool == bool\";\n    logicCompare.desc = \"Compare for logical equality\";\n    logicCompare.prototype.onExecute = function() {\n        var last = null;\n        var ret = true;\n        for (var inX in this.inputs){\n            if (last === null) last = this.getInputData(inX);\n            else\n                if (last != this.getInputData(inX)){\n                    ret = false;\n                    break;\n                }\n        }\n        this.setOutputData(0, ret);\n    };\n    logicCompare.prototype.onGetInputs = function() {\n        return [\n            [\"bool\", \"boolean\"]\n        ];\n    };\n    LiteGraph.registerNodeType(\"logic/CompareBool\", logicCompare);\n    \n    \n    function logicBranch(){\n        this.properties = { };\n        this.addInput(\"onTrigger\", LiteGraph.ACTION);\n        this.addInput(\"condition\", \"boolean\");\n        this.addOutput(\"true\", LiteGraph.EVENT);\n        this.addOutput(\"false\", LiteGraph.EVENT);\n        this.mode = LiteGraph.ON_TRIGGER;\n    }\n    logicBranch.title = \"Branch\";\n    logicBranch.desc = \"Branch execution on condition\";\n    logicBranch.prototype.onExecute = function(param, options) {\n        var condtition = this.getInputData(1);\n        if (condtition){\n            this.triggerSlot(0);\n        }else{\n            this.triggerSlot(1);\n        }\n    };\n    LiteGraph.registerNodeType(\"logic/IF\", logicBranch);\n})(this);\n\n//event related nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    function LGWebSocket() {\r\n        this.size = [60, 20];\r\n        this.addInput(\"send\", LiteGraph.ACTION);\r\n        this.addOutput(\"received\", LiteGraph.EVENT);\r\n        this.addInput(\"in\", 0);\r\n        this.addOutput(\"out\", 0);\r\n        this.properties = {\r\n            url: \"\",\r\n            room: \"lgraph\", //allows to filter messages,\r\n            only_send_changes: true\r\n        };\r\n        this._ws = null;\r\n        this._last_sent_data = [];\r\n        this._last_received_data = [];\r\n    }\r\n\r\n    LGWebSocket.title = \"WebSocket\";\r\n    LGWebSocket.desc = \"Send data through a websocket\";\r\n\r\n    LGWebSocket.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"url\") {\r\n            this.connectSocket();\r\n        }\r\n    };\r\n\r\n    LGWebSocket.prototype.onExecute = function() {\r\n        if (!this._ws && this.properties.url) {\r\n            this.connectSocket();\r\n        }\r\n\r\n        if (!this._ws || this._ws.readyState != WebSocket.OPEN) {\r\n            return;\r\n        }\r\n\r\n        var room = this.properties.room;\r\n        var only_changes = this.properties.only_send_changes;\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var data = this.getInputData(i);\r\n            if (data == null) {\r\n                continue;\r\n            }\r\n            var json;\r\n            try {\r\n                json = JSON.stringify({\r\n                    type: 0,\r\n                    room: room,\r\n                    channel: i,\r\n                    data: data\r\n                });\r\n            } catch (err) {\r\n                continue;\r\n            }\r\n            if (only_changes && this._last_sent_data[i] == json) {\r\n                continue;\r\n            }\r\n\r\n            this._last_sent_data[i] = json;\r\n            this._ws.send(json);\r\n        }\r\n\r\n        for (var i = 1; i < this.outputs.length; ++i) {\r\n            this.setOutputData(i, this._last_received_data[i]);\r\n        }\r\n\r\n        if (this.boxcolor == \"#AFA\") {\r\n            this.boxcolor = \"#6C6\";\r\n        }\r\n    };\r\n\r\n    LGWebSocket.prototype.connectSocket = function() {\r\n        var that = this;\r\n        var url = this.properties.url;\r\n        if (url.substr(0, 2) != \"ws\") {\r\n            url = \"ws://\" + url;\r\n        }\r\n        this._ws = new WebSocket(url);\r\n        this._ws.onopen = function() {\r\n            console.log(\"ready\");\r\n            that.boxcolor = \"#6C6\";\r\n        };\r\n        this._ws.onmessage = function(e) {\r\n            that.boxcolor = \"#AFA\";\r\n            var data = JSON.parse(e.data);\r\n            if (data.room && data.room != that.properties.room) {\r\n                return;\r\n            }\r\n            if (data.type == 1) {\r\n                if (\r\n                    data.data.object_class &&\r\n                    LiteGraph[data.data.object_class]\r\n                ) {\r\n                    var obj = null;\r\n                    try {\r\n                        obj = new LiteGraph[data.data.object_class](data.data);\r\n                        that.triggerSlot(0, obj);\r\n                    } catch (err) {\r\n                        return;\r\n                    }\r\n                } else {\r\n                    that.triggerSlot(0, data.data);\r\n                }\r\n            } else {\r\n                that._last_received_data[data.channel || 0] = data.data;\r\n            }\r\n        };\r\n        this._ws.onerror = function(e) {\r\n            console.log(\"couldnt connect to websocket\");\r\n            that.boxcolor = \"#E88\";\r\n        };\r\n        this._ws.onclose = function(e) {\r\n            console.log(\"connection closed\");\r\n            that.boxcolor = \"#000\";\r\n        };\r\n    };\r\n\r\n    LGWebSocket.prototype.send = function(data) {\r\n        if (!this._ws || this._ws.readyState != WebSocket.OPEN) {\r\n            return;\r\n        }\r\n        this._ws.send(JSON.stringify({ type: 1, msg: data }));\r\n    };\r\n\r\n    LGWebSocket.prototype.onAction = function(action, param) {\r\n        if (!this._ws || this._ws.readyState != WebSocket.OPEN) {\r\n            return;\r\n        }\r\n        this._ws.send({\r\n            type: 1,\r\n            room: this.properties.room,\r\n            action: action,\r\n            data: param\r\n        });\r\n    };\r\n\r\n    LGWebSocket.prototype.onGetInputs = function() {\r\n        return [[\"in\", 0]];\r\n    };\r\n\r\n    LGWebSocket.prototype.onGetOutputs = function() {\r\n        return [[\"out\", 0]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"network/websocket\", LGWebSocket);\r\n\r\n    //It is like a websocket but using the SillyServer.js server that bounces packets back to all clients connected:\r\n    //For more information: https://github.com/jagenjo/SillyServer.js\r\n\r\n    function LGSillyClient() {\r\n        //this.size = [60,20];\r\n        this.room_widget = this.addWidget(\r\n            \"text\",\r\n            \"Room\",\r\n            \"lgraph\",\r\n            this.setRoom.bind(this)\r\n        );\r\n        this.addWidget(\r\n            \"button\",\r\n            \"Reconnect\",\r\n            null,\r\n            this.connectSocket.bind(this)\r\n        );\r\n\r\n        this.addInput(\"send\", LiteGraph.ACTION);\r\n        this.addOutput(\"received\", LiteGraph.EVENT);\r\n        this.addInput(\"in\", 0);\r\n        this.addOutput(\"out\", 0);\r\n        this.properties = {\r\n            url: \"tamats.com:55000\",\r\n            room: \"lgraph\",\r\n            only_send_changes: true\r\n        };\r\n\r\n        this._server = null;\r\n        this.connectSocket();\r\n        this._last_sent_data = [];\r\n        this._last_received_data = [];\r\n\r\n\t\tif(typeof(SillyClient) == \"undefined\")\r\n\t\t\tconsole.warn(\"remember to add SillyClient.js to your project: https://tamats.com/projects/sillyserver/src/sillyclient.js\");\r\n    }\r\n\r\n    LGSillyClient.title = \"SillyClient\";\r\n    LGSillyClient.desc = \"Connects to SillyServer to broadcast messages\";\r\n\r\n    LGSillyClient.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"room\") {\r\n            this.room_widget.value = value;\r\n        }\r\n        this.connectSocket();\r\n    };\r\n\r\n    LGSillyClient.prototype.setRoom = function(room_name) {\r\n        this.properties.room = room_name;\r\n        this.room_widget.value = room_name;\r\n        this.connectSocket();\r\n    };\r\n\r\n    //force label names\r\n    LGSillyClient.prototype.onDrawForeground = function() {\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var slot = this.inputs[i];\r\n            slot.label = \"in_\" + i;\r\n        }\r\n        for (var i = 1; i < this.outputs.length; ++i) {\r\n            var slot = this.outputs[i];\r\n            slot.label = \"out_\" + i;\r\n        }\r\n    };\r\n\r\n    LGSillyClient.prototype.onExecute = function() {\r\n        if (!this._server || !this._server.is_connected) {\r\n            return;\r\n        }\r\n\r\n        var only_send_changes = this.properties.only_send_changes;\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var data = this.getInputData(i);\r\n\t\t\tvar prev_data = this._last_sent_data[i];\r\n            if (data != null) {\r\n                if (only_send_changes)\r\n\t\t\t\t{\t\r\n\t\t\t\t\tvar is_equal = true;\r\n\t\t\t\t\tif( data && data.length && prev_data && prev_data.length == data.length && data.constructor !== String)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfor(var j = 0; j < data.length; ++j)\r\n\t\t\t\t\t\t\tif( prev_data[j] != data[j] )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tis_equal = false;\r\n\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if(this._last_sent_data[i] != data)\r\n\t\t\t\t\t\tis_equal = false;\r\n\t\t\t\t\tif(is_equal)\r\n\t\t\t\t\t\t\tcontinue;\r\n                }\r\n                this._server.sendMessage({ type: 0, channel: i, data: data });\r\n\t\t\t\tif( data.length && data.constructor !== String )\r\n\t\t\t\t{\r\n\t\t\t\t\tif( this._last_sent_data[i] )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis._last_sent_data[i].length = data.length;\r\n\t\t\t\t\t\tfor(var j = 0; j < data.length; ++j)\r\n\t\t\t\t\t\t\tthis._last_sent_data[i][j] = data[j];\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse //create\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif(data.constructor === Array)\r\n\t\t\t\t\t\t\tthis._last_sent_data[i] = data.concat();\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\tthis._last_sent_data[i] = new data.constructor( data );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t                this._last_sent_data[i] = data; //should be cloned\r\n            }\r\n        }\r\n\r\n        for (var i = 1; i < this.outputs.length; ++i) {\r\n            this.setOutputData(i, this._last_received_data[i]);\r\n        }\r\n\r\n        if (this.boxcolor == \"#AFA\") {\r\n            this.boxcolor = \"#6C6\";\r\n        }\r\n    };\r\n\r\n    LGSillyClient.prototype.connectSocket = function() {\r\n        var that = this;\r\n        if (typeof SillyClient == \"undefined\") {\r\n            if (!this._error) {\r\n                console.error(\r\n                    \"SillyClient node cannot be used, you must include SillyServer.js\"\r\n                );\r\n            }\r\n            this._error = true;\r\n            return;\r\n        }\r\n\r\n        this._server = new SillyClient();\r\n        this._server.on_ready = function() {\r\n            console.log(\"ready\");\r\n            that.boxcolor = \"#6C6\";\r\n        };\r\n        this._server.on_message = function(id, msg) {\r\n            var data = null;\r\n            try {\r\n                data = JSON.parse(msg);\r\n            } catch (err) {\r\n                return;\r\n            }\r\n\r\n            if (data.type == 1) {\r\n                //EVENT slot\r\n                if (\r\n                    data.data.object_class &&\r\n                    LiteGraph[data.data.object_class]\r\n                ) {\r\n                    var obj = null;\r\n                    try {\r\n                        obj = new LiteGraph[data.data.object_class](data.data);\r\n                        that.triggerSlot(0, obj);\r\n                    } catch (err) {\r\n                        return;\r\n                    }\r\n                } else {\r\n                    that.triggerSlot(0, data.data);\r\n                }\r\n            } //for FLOW slots\r\n            else {\r\n                that._last_received_data[data.channel || 0] = data.data;\r\n            }\r\n            that.boxcolor = \"#AFA\";\r\n        };\r\n        this._server.on_error = function(e) {\r\n            console.log(\"couldnt connect to websocket\");\r\n            that.boxcolor = \"#E88\";\r\n        };\r\n        this._server.on_close = function(e) {\r\n            console.log(\"connection closed\");\r\n            that.boxcolor = \"#000\";\r\n        };\r\n\r\n        if (this.properties.url && this.properties.room) {\r\n            try {\r\n                this._server.connect(this.properties.url, this.properties.room);\r\n            } catch (err) {\r\n                console.error(\"SillyServer error: \" + err);\r\n                this._server = null;\r\n                return;\r\n            }\r\n            this._final_url = this.properties.url + \"/\" + this.properties.room;\r\n        }\r\n    };\r\n\r\n    LGSillyClient.prototype.send = function(data) {\r\n        if (!this._server || !this._server.is_connected) {\r\n            return;\r\n        }\r\n        this._server.sendMessage({ type: 1, data: data });\r\n    };\r\n\r\n    LGSillyClient.prototype.onAction = function(action, param) {\r\n        if (!this._server || !this._server.is_connected) {\r\n            return;\r\n        }\r\n        this._server.sendMessage({ type: 1, action: action, data: param });\r\n    };\r\n\r\n    LGSillyClient.prototype.onGetInputs = function() {\r\n        return [[\"in\", 0]];\r\n    };\r\n\r\n    LGSillyClient.prototype.onGetOutputs = function() {\r\n        return [[\"out\", 0]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"network/sillyclient\", LGSillyClient);\r\n\r\n//HTTP Request\r\nfunction HTTPRequestNode() {\r\n\tvar that = this;\r\n\tthis.addInput(\"request\", LiteGraph.ACTION);\r\n\tthis.addInput(\"url\", \"string\");\r\n\tthis.addProperty(\"url\", \"\");\r\n\tthis.addOutput(\"ready\", LiteGraph.EVENT);\r\n    this.addOutput(\"data\", \"string\");\r\n\tthis.addWidget(\"button\", \"Fetch\", null, this.fetch.bind(this));\r\n\tthis._data = null;\r\n\tthis._fetching = null;\r\n}\r\n\r\nHTTPRequestNode.title = \"HTTP Request\";\r\nHTTPRequestNode.desc = \"Fetch data through HTTP\";\r\n\r\nHTTPRequestNode.prototype.fetch = function()\r\n{\r\n\tvar url = this.properties.url;\r\n\tif(!url)\r\n\t\treturn;\r\n\r\n\tthis.boxcolor = \"#FF0\";\r\n\tvar that = this;\r\n\tthis._fetching = fetch(url)\r\n\t.then(resp=>{\r\n\t\tif(!resp.ok)\r\n\t\t{\r\n\t\t\tthis.boxcolor = \"#F00\";\r\n\t\t\tthat.trigger(\"error\");\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.boxcolor = \"#0F0\";\r\n\t\t\treturn resp.text();\r\n\t\t}\r\n\t})\r\n\t.then(data=>{\r\n\t\tthat._data = data;\r\n\t\tthat._fetching = null;\r\n\t\tthat.trigger(\"ready\");\r\n\t});\r\n}\r\n\r\nHTTPRequestNode.prototype.onAction = function(evt)\r\n{\r\n\tif(evt == \"request\")\r\n\t\tthis.fetch();\r\n}\r\n\r\nHTTPRequestNode.prototype.onExecute = function() {\r\n\tthis.setOutputData(1, this._data);\r\n};\r\n\r\nHTTPRequestNode.prototype.onGetOutputs = function() {\r\n\treturn [[\"error\",LiteGraph.EVENT]];\r\n}\r\n\r\nLiteGraph.registerNodeType(\"network/httprequest\", HTTPRequestNode);\r\n\r\n\r\n\t\r\n})(this);\r\n\n"
  },
  {
    "path": "csharp/LiteGraph.cs",
    "content": "﻿using System;\r\nusing System.Collections;\r\nusing System.Collections.Generic;\r\n//using System.Diagnostics;\r\nusing UnityEngine; //for debug messages\r\nusing SimpleJSON; //to parse the JSON\r\n\r\n\r\nnamespace LiteGraph\r\n{\r\n    public enum DataType { NONE, ENUM, NUMBER, STRING, BOOL, VEC2, VEC3 };\r\n\r\n    public struct vec2 {\r\n        float x;\r\n        float y;\r\n    };\r\n\r\n    public struct vec3\r\n    {\r\n        float x;\r\n        float y;\r\n        float z;\r\n    };\r\n\r\n    //not used yet...\r\n    public class MutableType {\r\n        public DataType type;\r\n        public bool data_bool;\r\n        public float data_number;\r\n        public string data_string;\r\n        public vec2 data_vec2;\r\n        public vec3 data_vec3;\r\n        public bool AsBool { get { return data_bool; } }\r\n        public int AsEnum { get { return (int)data_number; } }\r\n        public float AsFloat { get { return data_number; } }\r\n        public string AsString { get { return data_string; } }\r\n        public vec2 AsVec2 { get { return data_vec2; } }\r\n        public vec3 AsVec3 { get { return data_vec3; } }\r\n        public void Set(bool v) { type = DataType.BOOL; data_bool = v; }\r\n        public void Set(int v) { type = DataType.ENUM; data_number = v; }\r\n        public void Set(float v) { type = DataType.NUMBER; data_number = v; }\r\n        public void Set(string v) { type = DataType.STRING; data_string = v; }\r\n        public void Set(vec2 v) { type = DataType.VEC2; data_vec2 = v; }\r\n        public void Set(vec3 v) { type = DataType.VEC3; data_vec3 = v; }\r\n        public override string ToString() {\r\n            switch (type)\r\n            {\r\n                case DataType.NONE: return \"\";\r\n                case DataType.ENUM: return data_number.ToString();\r\n                case DataType.BOOL: return data_bool.ToString();\r\n                case DataType.NUMBER: return data_number.ToString();\r\n                case DataType.STRING: return data_string; \r\n                case DataType.VEC2: return data_vec2.ToString();\r\n                case DataType.VEC3: return data_vec3.ToString();\r\n            }\r\n            return \"\";\r\n        }\r\n    };\r\n\r\n    //to store connection info\r\n    public class LLink {\r\n        public int id;\r\n        public int origin_id;\r\n        public int origin_slot;\r\n        public int target_id;\r\n        public int target_slot;\r\n\r\n        public DataType data_type;\r\n\r\n        public bool data_bool;\r\n        public float data_float;\r\n        public string data_string;\r\n        public vec2 data_vec2;\r\n        public vec3 data_vec3;\r\n\r\n        public LLink(int id, DataType type, int origin_id, int origin_slot, int target_id, int target_slot)\r\n        {\r\n            this.id = id;\r\n            this.data_type = type;\r\n            this.origin_id = origin_id;\r\n            this.origin_slot = origin_slot;\r\n            this.target_id = target_id;\r\n            this.target_slot = target_slot;\r\n        }\r\n\r\n        public void setData(bool data)\r\n        {\r\n            data_type = DataType.BOOL;\r\n            data_bool = data;\r\n        }\r\n\r\n        public void setData(float data)\r\n        {\r\n            data_type = DataType.NUMBER;\r\n            data_float = data;\r\n        }\r\n\r\n        public void setData(string data)\r\n        {\r\n            data_type = DataType.STRING;\r\n            data_string = data;\r\n        }\r\n\r\n        public void setData(vec2 data)\r\n        {\r\n            data_type = DataType.VEC2;\r\n            data_vec2 = data;\r\n        }\r\n\r\n        public void setData(vec3 data)\r\n        {\r\n            data_type = DataType.VEC3;\r\n            data_vec3 = data;\r\n        }\r\n\r\n    }\r\n\r\n    //to store slot info\r\n    public class LSlot\r\n    {\r\n        public int num;\r\n        public string name;\r\n        public DataType type;\r\n        public LLink link = null; //for input slots\r\n        public List<LLink> links = new List<LLink>(); //for output slots\r\n\r\n        public LSlot(string name, DataType type)\r\n        {\r\n            this.name = name;\r\n            this.type = type;\r\n        }\r\n    }\r\n\r\n    //the node base class\r\n    public class LGraphNode {\r\n        public int id = -1;\r\n\r\n        public LGraph graph = null;\r\n        public int order = -1;\r\n\r\n        public List<LSlot> inputs = new List<LSlot>();\r\n        public List<LSlot> outputs = new List<LSlot>();\r\n\r\n        public LGraphNode()\r\n        {\r\n        }\r\n\r\n        public virtual LSlot addInput(string name, DataType type = DataType.NONE)\r\n        {\r\n            LSlot slot = new LSlot(name, type);\r\n            slot.num = inputs.Count;\r\n            inputs.Add(slot);\r\n            return slot;\r\n        }\r\n\r\n        public virtual LSlot addOutput(string name, DataType type = DataType.NONE)\r\n        {\r\n            LSlot slot = new LSlot(name, type);\r\n            slot.num = outputs.Count;\r\n            outputs.Add(slot);\r\n            return slot;\r\n        }\r\n\r\n        public virtual bool getInputData(int slot_num, bool default_value)\r\n        {\r\n            LLink link = inputs[slot_num].link;\r\n            if (link != null)\r\n                return link.data_bool;\r\n            return default_value;\r\n        }\r\n\r\n        public virtual float getInputData(int slot_num, float default_value )\r\n        {\r\n            LLink link = inputs[slot_num].link;\r\n            if (link != null)\r\n                return link.data_float;\r\n            return default_value;\r\n        }\r\n\r\n        public virtual string getInputData(int slot_num, string default_value)\r\n        {\r\n            LLink link = inputs[slot_num].link;\r\n            if (link != null)\r\n                return link.data_string;\r\n            return default_value;\r\n        }\r\n\r\n        public virtual vec2 getInputData(int slot_num, vec2 default_value)\r\n        {\r\n            LLink link = inputs[slot_num].link;\r\n            if (link != null)\r\n                return link.data_vec2;\r\n            return default_value;\r\n        }\r\n\r\n        public virtual vec3 getInputData(int slot_num, vec3 default_value)\r\n        {\r\n            LLink link = inputs[slot_num].link;\r\n            if (link != null)\r\n                return link.data_vec3;\r\n            return default_value;\r\n        }\r\n\r\n        public virtual void setOutputData(int slot_num, bool v)\r\n        {\r\n            if (inputs.Count <= slot_num)\r\n                return;\r\n            LSlot slot = outputs[slot_num];\r\n            if (slot == null)\r\n                return;\r\n            for (int i = 0; i < slot.links.Count; ++i)\r\n            {\r\n                LLink link = slot.links[i];\r\n                if (link == null)\r\n                    return;\r\n                link.setData(v);\r\n            }\r\n        }\r\n\r\n        public virtual void setOutputData(int slot_num, float v)\r\n        {\r\n            if (outputs.Count <= slot_num)\r\n                return;\r\n            LSlot slot = outputs[slot_num];\r\n            if (slot == null)\r\n                return;\r\n            for (int i = 0; i < slot.links.Count; ++i)\r\n            {\r\n                LLink link = slot.links[i];\r\n                if (link == null)\r\n                    return;\r\n                link.setData(v);\r\n            }\r\n        }\r\n\r\n        public virtual void setOutputData(int slot_num, string v)\r\n        {\r\n            if (outputs.Count <= slot_num)\r\n                return;\r\n            LSlot slot = outputs[slot_num];\r\n            if (slot == null)\r\n                return;\r\n            for (int i = 0; i < slot.links.Count; ++i)\r\n            {\r\n                LLink link = slot.links[i];\r\n                if (link == null)\r\n                    return;\r\n                link.setData(v);\r\n            }\r\n        }\r\n\r\n        public virtual void setOutputData(int slot_num, vec2 v)\r\n        {\r\n            if (outputs.Count <= slot_num)\r\n                return;\r\n            LSlot slot = outputs[slot_num];\r\n            if (slot == null)\r\n                return;\r\n            for (int i = 0; i < slot.links.Count; ++i)\r\n            {\r\n                LLink link = slot.links[i];\r\n                if (link == null)\r\n                    return;\r\n                link.setData(v);\r\n            }\r\n        }\r\n\r\n        public virtual void setOutputData(int slot_num, vec3 v)\r\n        {\r\n            if (outputs.Count <= slot_num)\r\n                return;\r\n            LSlot slot = outputs[slot_num];\r\n            if (slot == null)\r\n                return;\r\n            for (int i = 0; i < slot.links.Count; ++i)\r\n            {\r\n                LLink link = slot.links[i];\r\n                if (link == null)\r\n                    return;\r\n                link.setData(v);\r\n            }\r\n        }\r\n\r\n        public virtual void transferData(int input_slot_num, int output_slot_num)\r\n        {\r\n            LLink input_link = inputs[input_slot_num].link;\r\n            if (input_link == null)\r\n                return;\r\n\r\n            if (outputs.Count <= output_slot_num)\r\n                return;\r\n            LSlot slot = outputs[output_slot_num];\r\n            if (slot == null)\r\n                return;\r\n            for (int i = 0; i < slot.links.Count; ++i)\r\n            {\r\n                LLink link = slot.links[i];\r\n                if (link == null)\r\n                    return;\r\n                switch(input_link.data_type)\r\n                {\r\n                    case DataType.BOOL: link.setData(input_link.data_bool); break;\r\n                    case DataType.NUMBER: link.setData(input_link.data_float); break;\r\n                    case DataType.STRING: link.setData(input_link.data_string); break;\r\n                    case DataType.VEC2: link.setData(input_link.data_vec2); break;\r\n                    case DataType.VEC3: link.setData(input_link.data_vec3); break;\r\n                }\r\n            }\r\n        }\r\n\r\n        public virtual bool connect(int origin_slot, LGraphNode target, int target_slot)\r\n        {\r\n            if(graph == null)\r\n                throw (new Exception(\"node does not belong to a graph\"));\r\n            if (graph != target.graph)\r\n                throw (new Exception(\"nodes do not belong to same graph\") );\r\n\r\n            LSlot origin_slot_info = this.outputs[origin_slot];\r\n            LSlot target_slot_info = target.inputs[target_slot];\r\n            if (origin_slot_info == null || target_slot_info == null)\r\n                return false;\r\n\r\n            if(origin_slot_info.type != target_slot_info.type && (origin_slot_info.type != DataType.NONE && target_slot_info.type != DataType.NONE) )\r\n                throw (new Exception(\"connecting incompatible types\"));\r\n\r\n            int id = graph.last_link_id++;\r\n            LLink link = new LLink(id, origin_slot_info.type, this.id, origin_slot, target.id, target_slot);\r\n\r\n            graph.links.Add(link);\r\n            origin_slot_info.links.Add(link);\r\n            target_slot_info.link = link;\r\n\r\n            graph.sortByExecutionOrder();\r\n            return true;\r\n        }\r\n\r\n        public virtual void onExecute()\r\n        {\r\n        }\r\n\r\n        public virtual void configure(JSONNode json_node)\r\n        {\r\n            this.id = json_node[\"id\"].AsInt;\r\n            this.order = json_node[\"order\"].AsInt;\r\n\r\n            //inputs\r\n            var json_inputs = json_node[\"inputs\"];\r\n            if (json_inputs != null)\r\n            {\r\n                JSONNode.Enumerator it = json_inputs.GetEnumerator();\r\n                int i = 0;\r\n                while(it.MoveNext())\r\n                {\r\n                    JSONNode json_slot = it.Current;\r\n                    string str_type = json_slot[\"type\"];\r\n                    DataType type = DataType.NONE;\r\n                    if (str_type != null && Globals.stringToDataType.ContainsKey(str_type))\r\n                        type = Globals.stringToDataType[str_type];\r\n                    LSlot slot = null;\r\n                    if (inputs.Count > i)\r\n                        slot = inputs[i];\r\n                    if(slot == null)\r\n                        slot = this.addInput( json_slot[\"name\"], type );\r\n                    JSONNode json_link = json_slot[\"link\"];\r\n                    if (json_link != null)\r\n                        slot.link = graph.links_by_id[json_link.AsInt];\r\n                    ++i;\r\n                }\r\n            }\r\n\r\n            //outputs\r\n            var json_outputs = json_node[\"outputs\"];\r\n            if (json_outputs != null)\r\n            {\r\n                JSONNode.Enumerator it = json_outputs.GetEnumerator();\r\n                int i = 0;\r\n                while (it.MoveNext())\r\n                {\r\n                    JSONNode json_slot = it.Current;\r\n                    string str_type = json_slot[\"type\"];\r\n                    DataType type = DataType.NONE;\r\n                    if (str_type != null && Globals.stringToDataType.ContainsKey(str_type))\r\n                        type = Globals.stringToDataType[str_type];\r\n                    LSlot slot = null;\r\n                    if (outputs.Count > i)\r\n                        slot = outputs[i];\r\n                    if (slot == null)\r\n                        slot = this.addOutput(json_slot[\"name\"], type);\r\n\r\n                    JSONNode json_links = json_slot[\"links\"];\r\n                    if(json_links != null)\r\n                    {\r\n                        JSONNode.Enumerator it2 = json_links.GetEnumerator();\r\n                        while (it2.MoveNext())\r\n                        {\r\n                            JSONNode json_link_id = it2.Current;\r\n                            LLink link = graph.links_by_id[json_link_id.AsInt];\r\n                            if (link != null)\r\n                                slot.links.Add(link);\r\n                            else\r\n                                Debug.LogError(\"Link ID not found!: \" + json_link_id);\r\n                        }\r\n                    }\r\n                    ++i;\r\n                }\r\n            }\r\n\r\n            //custom data (properties)\r\n            this.onConfigure(json_node);\r\n        }\r\n\r\n        public virtual void onConfigure(JSONNode json_node)\r\n        {\r\n            //overwrite this one\r\n        }\r\n    }\r\n\r\n    //namespace to store global litegraph data\r\n    public class Globals\r\n    {\r\n        static public Dictionary<string, DataType> stringToDataType = new Dictionary<string, DataType> { { \"NONE\", DataType.NONE }, { \"\", DataType.NONE }, { \"ENUM\", DataType.ENUM }, {\"NUMBER\",DataType.NUMBER }, { \"number\", DataType.NUMBER }, { \"BOOLEAN\", DataType.BOOL }, { \"boolean\", DataType.BOOL }, { \"STRING\", DataType.STRING }, { \"string\", DataType.STRING }, { \"VEC2\", DataType.VEC2 } };\r\n\r\n        static public Dictionary<string, Func<LGraphNode>> node_types = new Dictionary<string, Func<LGraphNode>>();\r\n        static public void registerType(string name, Func<LGraphNode> ctor )\r\n        {\r\n            node_types.Add(name, ctor);\r\n        }\r\n        static public LGraphNode createNodeType(string name)\r\n        {\r\n            if (!node_types.ContainsKey(name))\r\n            {\r\n                Debug.Log(\"Node type not found: \" + name);\r\n                return null;\r\n            }\r\n            Func<LGraphNode> ctor = node_types[name];\r\n            return ctor();\r\n        }\r\n\r\n    };\r\n\r\n    //one graph\r\n    public class LGraph\r\n    {\r\n        public List<LGraphNode> nodes = new List<LGraphNode>();\r\n        public Dictionary<int, LGraphNode> nodes_by_id = new Dictionary<int, LGraphNode>();\r\n        public List<LGraphNode> nodes_in_execution_order = new List<LGraphNode>();\r\n        public List<LLink> links = new List<LLink>();\r\n        public Dictionary<int,LLink> links_by_id = new Dictionary<int, LLink>();\r\n\r\n        public bool has_errors = false;\r\n        public int last_node_id = 0;\r\n        public int last_link_id = 0;\r\n        public double time = 0; //time in seconds\r\n\r\n        public Dictionary<string, float> outputs = new Dictionary<string, float>(); \r\n\r\n        // Start is called before the first frame update\r\n        public LGraph()\r\n        {\r\n        }\r\n\r\n        public void add(LGraphNode node)\r\n        {\r\n            if (node.graph != null)\r\n                throw ( new Exception(\"already has graph\") );\r\n\r\n            node.graph = this;\r\n            node.id = last_node_id++;\r\n            node.order = node.id;\r\n            nodes.Add(node);\r\n            nodes_by_id.Add(node.id, node);\r\n        }\r\n\r\n        public void clear()\r\n        {\r\n            has_errors = false;\r\n            nodes.Clear();\r\n            nodes_by_id.Clear();\r\n            links.Clear();\r\n            links_by_id.Clear();\r\n            nodes_in_execution_order.Clear();\r\n            last_link_id = 0;\r\n            last_node_id = 0;\r\n            outputs.Clear();\r\n        }\r\n\r\n        // Update is called once per frame\r\n        public void runStep(float dt = 0)\r\n        {\r\n            for (int i = 0; i < nodes_in_execution_order.Count; ++i)\r\n            {\r\n                LGraphNode node = nodes_in_execution_order[i];\r\n                node.onExecute();\r\n            }\r\n            time += dt;\r\n        }\r\n\r\n        public void configure(string data)\r\n        {\r\n            sortByExecutionOrder();\r\n        }\r\n\r\n        public void sortByExecutionOrder()\r\n        {\r\n            nodes_in_execution_order = nodes.GetRange(0, nodes.Count);\r\n\r\n            nodes_in_execution_order.Sort(delegate (LGraphNode a, LGraphNode b)\r\n            {\r\n                return a.order - b.order;\r\n            });\r\n        }\r\n\r\n        public void fromJSONText(string text)\r\n        {\r\n            clear();\r\n\r\n            var root = JSON.Parse(text);\r\n            last_node_id = root[\"last_node_id\"].AsInt;\r\n            last_link_id = root[\"last_link_id\"].AsInt;\r\n\r\n            var json_links = root[\"links\"];\r\n            for (int i = 0; i < json_links.Count; ++i)\r\n            {\r\n                var json_node = json_links[i];\r\n                int id = json_node[0].AsInt;\r\n                int origin_id = json_node[1].AsInt;\r\n                int origin_slot = json_node[2].AsInt;\r\n                int target_id = json_node[3].AsInt;\r\n                int target_slot = json_node[4].AsInt;\r\n                JSONNode json_type = json_node[5];\r\n                DataType type = DataType.NONE;\r\n\r\n                if(json_type != null && json_type.Value != \"0\" && Globals.stringToDataType.ContainsKey(json_type) )\r\n                    type = Globals.stringToDataType[ json_type ];\r\n\r\n                LLink link = new LLink(id, type, origin_id, origin_slot, target_id, target_slot);\r\n                links.Add(link);\r\n                links_by_id[link.id] = link;\r\n            }\r\n\r\n            var json_nodes = root[\"nodes\"];\r\n            for (int i = 0; i < json_nodes.Count; ++i)\r\n            {\r\n                var json_node = json_nodes[i];\r\n                string node_type = json_node[\"type\"];\r\n                Debug.Log(node_type);\r\n                LGraphNode node = LiteGraph.Globals.createNodeType(node_type);\r\n                if (node == null)\r\n                {\r\n                    Debug.Log(\"Error: node type not found: \" + node_type);\r\n                    has_errors = true;\r\n                    continue;\r\n                }\r\n                node.graph = this;\r\n                nodes.Add(node);\r\n                node.configure(json_node);\r\n            }\r\n\r\n            sortByExecutionOrder();\r\n        }\r\n\r\n        public float getOutput(string name, float def_value)\r\n        {\r\n            if (!outputs.ContainsKey(name))\r\n                return def_value;\r\n            return outputs[name];\r\n        }\r\n\r\n    }\r\n\r\n    public class Main\r\n    {\r\n        public static void Init()\r\n        {\r\n            Main.loadNodes();\r\n        }\r\n\r\n        public static void loadNodes()\r\n        {\r\n            Globals.registerType(WatchNode.type, () => new WatchNode() );\r\n            Globals.registerType(RandomNumberNode.type, () => new RandomNumberNode());\r\n            Globals.registerType(ConstNumberNode.type, () => new ConstNumberNode());\r\n            Globals.registerType(TimeNode.type, () => new TimeNode());\r\n            Globals.registerType(ConditionNode.type, () => new ConditionNode());\r\n            Globals.registerType(GraphOutputNode.type, () => new GraphOutputNode());\r\n            Globals.registerType(GateNode.type, () => new GateNode());\r\n\r\n        }\r\n\r\n        public static void test()\r\n        {\r\n            Debug.Log(\"Testing Graph...\");\r\n            LGraph graph = new LGraph();\r\n            LGraphNode node1 = LiteGraph.Globals.createNodeType(\"math/rand\");\r\n            graph.add(node1);\r\n            LGraphNode node2 = LiteGraph.Globals.createNodeType(\"basic/watch\");\r\n            graph.add(node2);\r\n            node1.connect(0,node2,0);\r\n\r\n            for(int i = 0; i < 100; ++i)\r\n                graph.runStep();\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "csharp/LiteGraphNodes.cs",
    "content": "﻿using System;\r\nusing System.Collections;\r\nusing System.Collections.Generic;\r\nusing UnityEngine;\r\n\r\nusing SimpleJSON;\r\n\r\nnamespace LiteGraph\r\n{\r\n    public class ConstNumberNode : LGraphNode\r\n    {\r\n        public static string type = \"basic/const\";\r\n\r\n        public float value = 0;\r\n\r\n        public ConstNumberNode()\r\n        {\r\n            this.addOutput(\"out\", DataType.NUMBER );\r\n        }\r\n\r\n        override public void onExecute()\r\n        {\r\n            this.setOutputData(0, value );\r\n        }\r\n\r\n        override public void onConfigure( JSONNode o)\r\n        {\r\n            JSONNode json_properties = o[\"properties\"];\r\n            value = json_properties[\"value\"].AsFloat;\r\n        }\r\n    }\r\n\r\n    public class RandomNumberNode : LGraphNode\r\n    {\r\n        public static string type = \"math/rand\";\r\n\r\n        float min_value = 0;\r\n        float max_value = 1;\r\n        System.Random random = new System.Random();\r\n\r\n        public RandomNumberNode()\r\n        {\r\n            this.addOutput(\"out\", DataType.NUMBER );\r\n        }\r\n\r\n        override public void onExecute()\r\n        {\r\n            this.setOutputData(0, (float)(min_value + random.NextDouble() * (max_value - min_value)) );\r\n        }\r\n\r\n        override public void onConfigure(JSONNode o)\r\n        {\r\n            JSONNode json_properties = o[\"properties\"];\r\n            min_value = json_properties[\"min\"].AsFloat;\r\n            max_value = json_properties[\"max\"].AsFloat;\r\n        }\r\n    }\r\n\r\n    public class GraphOutputNode : LGraphNode\r\n    {\r\n        public static string type = \"graph/output\";\r\n\r\n        string name = \"\";\r\n        DataType datatype = DataType.NUMBER;\r\n\r\n        public GraphOutputNode()\r\n        {\r\n            this.addInput(\"out\", datatype);\r\n        }\r\n\r\n        override public void onExecute()\r\n        {\r\n            switch (datatype)\r\n            {\r\n                case DataType.NUMBER:\r\n                    float v = this.getInputData(0, 0);\r\n                    graph.outputs[name] = v;\r\n                    break;\r\n            }\r\n        }\r\n\r\n        override public void onConfigure(JSONNode o)\r\n        {\r\n            JSONNode json_properties = o[\"properties\"];\r\n            name = json_properties[\"name\"];\r\n            if (json_properties.HasKey(\"type\"))\r\n            {\r\n                string str_type = json_properties[\"type\"];\r\n                if (Globals.stringToDataType.ContainsKey(str_type))\r\n                {\r\n                    datatype = Globals.stringToDataType[str_type];\r\n                    this.inputs[0].type = datatype;\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    public class ConditionNode : LGraphNode\r\n    {\r\n        public static string type = \"math/condition\";\r\n\r\n        public enum OPERATION { NONE,GREATER,LOWER,EQUAL,NEQUAL,GEQUAL,LEQUAL,OR,AND };\r\n        static public Dictionary<string, OPERATION> strToOperation = new Dictionary<string, OPERATION> { { \"NONE\", OPERATION.NONE }, { \">\", OPERATION.GREATER }, { \"<\", OPERATION.LOWER }, { \"==\", OPERATION.EQUAL }, { \"!=\", OPERATION.NEQUAL }, { \"<=\", OPERATION.LEQUAL }, { \">=\", OPERATION.GEQUAL }, { \"&&\", OPERATION.AND }, { \"||\", OPERATION.OR } };\r\n\r\n        public float A = 0;\r\n        public float B = 0;\r\n        public OPERATION OP = OPERATION.EQUAL;\r\n\r\n        public ConditionNode()\r\n        {\r\n            this.addOutput(\"A\", DataType.NUMBER );\r\n            this.addOutput(\"B\", DataType.NUMBER );\r\n            this.addOutput(\"out\", DataType.BOOL );\r\n        }\r\n\r\n        override public void onExecute()\r\n        {\r\n            float A = this.getInputData(0,this.A);\r\n            float B = this.getInputData(1,this.B);\r\n\r\n            bool v = false;\r\n            switch(OP)\r\n            {\r\n                case OPERATION.NONE: v = false; break;\r\n                case OPERATION.GREATER: v = A > B; break;\r\n                case OPERATION.LOWER: v = A < B; break;\r\n                case OPERATION.EQUAL: v = A == B; break;\r\n                case OPERATION.NEQUAL: v = A != B; break;\r\n                case OPERATION.GEQUAL: v = A >= B; break;\r\n                case OPERATION.LEQUAL: v = A <= B; break;\r\n                case OPERATION.OR: v = (A != 0) || (B != 0); break;\r\n                case OPERATION.AND: v = (A != 0) && (B != 0); break;\r\n            }\r\n            this.setOutputData(0, v);\r\n        }\r\n\r\n        override public void onConfigure(JSONNode o)\r\n        {\r\n            JSONNode json_properties = o[\"properties\"];\r\n\r\n            A = json_properties[\"A\"].AsFloat;\r\n            B = json_properties[\"B\"].AsFloat;\r\n            string op = json_properties[\"OP\"];\r\n            if (strToOperation.ContainsKey(op))\r\n                OP = strToOperation[op];\r\n            else\r\n                Debug.Log(\"Wrong operation type: \" + op);\r\n        }\r\n    }\r\n\r\n    public class GateNode : LGraphNode\r\n    {\r\n        public static string type = \"math/gate\";\r\n\r\n        public GateNode()\r\n        {\r\n            this.addInput(\"v\", DataType.BOOL);\r\n            this.addInput(\"A\");\r\n            this.addInput(\"B\");\r\n            this.addOutput(\"out\");\r\n        }\r\n\r\n        override public void onExecute()\r\n        {\r\n            bool v = this.getInputData(0, true);\r\n            this.transferData(v ? 1 : 2, 0);\r\n        }\r\n    }\r\n\r\n    public class TimeNode : LGraphNode\r\n    {\r\n        public static string type = \"basic/time\";\r\n\r\n        public TimeNode()\r\n        {\r\n            this.addOutput(\"in ms\", DataType.NUMBER );\r\n            this.addOutput(\"in sec\", DataType.NUMBER);\r\n        }\r\n\r\n        override public void onExecute()\r\n        {\r\n            this.setOutputData(0, (float)(graph.time * 1000));\r\n            this.setOutputData(1, (float)graph.time);\r\n        }\r\n    }\r\n\r\n\r\n    public class WatchNode : LGraphNode\r\n    {\r\n        public static string type = \"basic/watch\";\r\n\r\n        public WatchNode()\r\n        {\r\n            this.addInput(\"in\", DataType.NUMBER);\r\n        }\r\n\r\n        override public void onExecute()\r\n        {\r\n            float v = this.getInputData(0, 0);\r\n            //Debug.Log(\"Watch: \" + v.ToString());\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "csharp/LiteGraphTest.cs",
    "content": "﻿using System.Collections;\r\nusing System.Collections.Generic;\r\nusing UnityEngine;\r\n\r\nusing LiteGraph;\r\n\r\n\r\npublic class LiteGraphTest : MonoBehaviour\r\n{\r\n\r\n    public TextAsset graph_file = null;\r\n\r\n    private LGraph graph = null;\r\n    public bool graph_has_errors = false;\r\n\r\n    public float output_value = 0;\r\n\r\n    // Start is called before the first frame update\r\n    void Start()\r\n    {\r\n        System.Diagnostics.Debug.WriteLine(\"Test!\");\r\n        LiteGraph.Main.Init();\r\n\r\n        graph = new LGraph();\r\n\r\n\r\n        if (!graph_file)\r\n        {\r\n            Debug.Log(\"Testing Base Graph...\");\r\n            LGraphNode node1 = LiteGraph.Globals.createNodeType(\"math/rand\");\r\n            graph.add(node1);\r\n            LGraphNode node2 = LiteGraph.Globals.createNodeType(\"basic/watch\");\r\n            graph.add(node2);\r\n            node1.connect(0, node2, 0);\r\n        }\r\n        else {\r\n            Debug.Log(\"Testing File Graph...\");\r\n            string text = graph_file.text;\r\n            graph.fromJSONText(text);\r\n        }\r\n\r\n        graph_has_errors = graph.has_errors;\r\n\r\n    }\r\n\r\n    // Update is called once per frame\r\n    void Update()\r\n    {\r\n        if(graph != null)\r\n            graph.runStep( Time.deltaTime );\r\n        output_value = graph.getOutput(\"output\",0);\r\n    }\r\n}\r\n"
  },
  {
    "path": "csharp/SimpleJSON.cs",
    "content": "/* * * * *\n * A simple JSON Parser / builder\n * ------------------------------\n * \n * It mainly has been written as a simple JSON parser. It can build a JSON string\n * from the node-tree, or generate a node tree from any valid JSON string.\n * \n * Written by Bunny83 \n * 2012-06-09\n * \n * [2012-06-09 First Version]\n * - provides strongly typed node classes and lists / dictionaries\n * - provides easy access to class members / array items / data values\n * - the parser now properly identifies types. So generating JSON with this framework should work.\n * - only double quotes (\") are used for quoting strings.\n * - provides \"casting\" properties to easily convert to / from those types:\n *   int / float / double / bool\n * - provides a common interface for each node so no explicit casting is required.\n * - the parser tries to avoid errors, but if malformed JSON is parsed the result is more or less undefined\n * - It can serialize/deserialize a node tree into/from an experimental compact binary format. It might\n *   be handy if you want to store things in a file and don't want it to be easily modifiable\n * \n * [2012-12-17 Update]\n * - Added internal JSONLazyCreator class which simplifies the construction of a JSON tree\n *   Now you can simple reference any item that doesn't exist yet and it will return a JSONLazyCreator\n *   The class determines the required type by it's further use, creates the type and removes itself.\n * - Added binary serialization / deserialization.\n * - Added support for BZip2 zipped binary format. Requires the SharpZipLib ( http://www.icsharpcode.net/opensource/sharpziplib/ )\n *   The usage of the SharpZipLib library can be disabled by removing or commenting out the USE_SharpZipLib define at the top\n * - The serializer uses different types when it comes to store the values. Since my data values\n *   are all of type string, the serializer will \"try\" which format fits best. The order is: int, float, double, bool, string.\n *   It's not the most efficient way but for a moderate amount of data it should work on all platforms.\n * \n * [2017-03-08 Update]\n * - Optimised parsing by using a StringBuilder for token. This prevents performance issues when large\n *   string data fields are contained in the json data.\n * - Finally refactored the badly named JSONClass into JSONObject.\n * - Replaced the old JSONData class by distict typed classes ( JSONString, JSONNumber, JSONBool, JSONNull ) this\n *   allows to propertly convert the node tree back to json without type information loss. The actual value\n *   parsing now happens at parsing time and not when you actually access one of the casting properties.\n * \n * [2017-04-11 Update]\n * - Fixed parsing bug where empty string values have been ignored.\n * - Optimised \"ToString\" by using a StringBuilder internally. This should heavily improve performance for large files\n * - Changed the overload of \"ToString(string aIndent)\" to \"ToString(int aIndent)\"\n * \n * [2017-11-29 Update]\n * - Removed the IEnumerator implementations on JSONArray & JSONObject and replaced it with a common\n *   struct Enumerator in JSONNode that should avoid garbage generation. The enumerator always works\n *   on KeyValuePair<string, JSONNode>, even for JSONArray.\n * - Added two wrapper Enumerators that allows for easy key or value enumeration. A JSONNode now has\n *   a \"Keys\" and a \"Values\" enumerable property. Those are also struct enumerators / enumerables\n * - A KeyValuePair<string, JSONNode> can now be implicitly converted into a JSONNode. This allows\n *   a foreach loop over a JSONNode to directly access the values only. Since KeyValuePair as well as\n *   all the Enumerators are structs, no garbage is allocated.\n * - To add Linq support another \"LinqEnumerator\" is available through the \"Linq\" property. This\n *   enumerator does implement the generic IEnumerable interface so most Linq extensions can be used\n *   on this enumerable object. This one does allocate memory as it's a wrapper class.\n * - The Escape method now escapes all control characters (# < 32) in strings as uncode characters\n *   (\\uXXXX) and if the static bool JSONNode.forceASCII is set to true it will also escape all\n *   characters # > 127. This might be useful if you require an ASCII output. Though keep in mind\n *   when your strings contain many non-ascii characters the strings become much longer (x6) and are\n *   no longer human readable.\n * - The node types JSONObject and JSONArray now have an \"Inline\" boolean switch which will default to\n *   false. It can be used to serialize this element inline even you serialize with an indented format\n *   This is useful for arrays containing numbers so it doesn't place every number on a new line\n * - Extracted the binary serialization code into a seperate extension file. All classes are now declared\n *   as \"partial\" so an extension file can even add a new virtual or abstract method / interface to\n *   JSONNode and override it in the concrete type classes. It's of course a hacky approach which is\n *   generally not recommended, but i wanted to keep everything tightly packed.\n * - Added a static CreateOrGet method to the JSONNull class. Since this class is immutable it could\n *   be reused without major problems. If you have a lot null fields in your data it will help reduce\n *   the memory / garbage overhead. I also added a static setting (reuseSameInstance) to JSONNull\n *   (default is true) which will change the behaviour of \"CreateOrGet\". If you set this to false\n *   CreateOrGet will not reuse the cached instance but instead create a new JSONNull instance each time.\n *   I made the JSONNull constructor private so if you need to create an instance manually use\n *   JSONNull.CreateOrGet()\n * \n * [2018-01-09 Update]\n * - Changed all double.TryParse and double.ToString uses to use the invariant culture to avoid problems\n *   on systems with a culture that uses a comma as decimal point.\n * \n * [2018-01-26 Update]\n * - Added AsLong. Note that a JSONNumber is stored as double and can't represent all long values. However\n *   storing it as string would work.\n * - Added static setting \"JSONNode.longAsString\" which controls the default type that is used by the\n *   LazyCreator when using AsLong\n * \n * [2018-04-25 Update]\n *  - Added support for parsing single values (JSONBool, JSONString, JSONNumber, JSONNull) as top level value.\n * \n * [2019-02-18 Update]\n *  - Added HasKey(key) and GetValueOrDefault(key, default) to the JSONNode class to provide way to read\n *    values conditionally without creating a LazyCreator\n * \n * [2019-03-25 Update]\n *  - Added static setting \"allowLineComments\" to the JSONNode class which is true by default. This allows\n *    \"//\" line comments when parsing json text as long as it's not within quoted text. All text after // up\n *    to the end of the line is completely ignored / skipped. This makes it easier to create human readable\n *    and editable files. Note that stripped comments are not read, processed or preserved in any way. So\n *    this feature is only relevant for human created files.\n *  - Explicitly strip BOM (Byte Order Mark) when parsing to avoid getting it leaked into a single primitive\n *    value. That's a rare case but better safe than sorry.\n *  - Allowing adding the empty string as key\n * \n * The MIT License (MIT)\n * \n * Copyright (c) 2012-2017 Markus Göbel (Bunny83)\n * \n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n * \n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n * \n * * * * */\nusing System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Linq;\nusing System.Text;\n\nnamespace SimpleJSON\n{\n    public enum JSONNodeType\n    {\n        Array = 1,\n        Object = 2,\n        String = 3,\n        Number = 4,\n        NullValue = 5,\n        Boolean = 6,\n        None = 7,\n        Custom = 0xFF,\n    }\n    public enum JSONTextMode\n    {\n        Compact,\n        Indent\n    }\n\n    public abstract partial class JSONNode\n    {\n        #region Enumerators\n        public struct Enumerator\n        {\n            private enum Type { None, Array, Object }\n            private Type type;\n            private Dictionary<string, JSONNode>.Enumerator m_Object;\n            private List<JSONNode>.Enumerator m_Array;\n            public bool IsValid { get { return type != Type.None; } }\n            public Enumerator(List<JSONNode>.Enumerator aArrayEnum)\n            {\n                type = Type.Array;\n                m_Object = default(Dictionary<string, JSONNode>.Enumerator);\n                m_Array = aArrayEnum;\n            }\n            public Enumerator(Dictionary<string, JSONNode>.Enumerator aDictEnum)\n            {\n                type = Type.Object;\n                m_Object = aDictEnum;\n                m_Array = default(List<JSONNode>.Enumerator);\n            }\n            public KeyValuePair<string, JSONNode> Current\n            {\n                get {\n                    if (type == Type.Array)\n                        return new KeyValuePair<string, JSONNode>(string.Empty, m_Array.Current);\n                    else if (type == Type.Object)\n                        return m_Object.Current;\n                    return new KeyValuePair<string, JSONNode>(string.Empty, null);\n                }\n            }\n            public bool MoveNext()\n            {\n                if (type == Type.Array)\n                    return m_Array.MoveNext();\n                else if (type == Type.Object)\n                    return m_Object.MoveNext();\n                return false;\n            }\n        }\n        public struct ValueEnumerator\n        {\n            private Enumerator m_Enumerator;\n            public ValueEnumerator(List<JSONNode>.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { }\n            public ValueEnumerator(Dictionary<string, JSONNode>.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { }\n            public ValueEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; }\n            public JSONNode Current { get { return m_Enumerator.Current.Value; } }\n            public bool MoveNext() { return m_Enumerator.MoveNext(); }\n            public ValueEnumerator GetEnumerator() { return this; }\n        }\n        public struct KeyEnumerator\n        {\n            private Enumerator m_Enumerator;\n            public KeyEnumerator(List<JSONNode>.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { }\n            public KeyEnumerator(Dictionary<string, JSONNode>.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { }\n            public KeyEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; }\n            public string Current { get { return m_Enumerator.Current.Key; } }\n            public bool MoveNext() { return m_Enumerator.MoveNext(); }\n            public KeyEnumerator GetEnumerator() { return this; }\n        }\n\n        public class LinqEnumerator : IEnumerator<KeyValuePair<string, JSONNode>>, IEnumerable<KeyValuePair<string, JSONNode>>\n        {\n            private JSONNode m_Node;\n            private Enumerator m_Enumerator;\n            internal LinqEnumerator(JSONNode aNode)\n            {\n                m_Node = aNode;\n                if (m_Node != null)\n                    m_Enumerator = m_Node.GetEnumerator();\n            }\n            public KeyValuePair<string, JSONNode> Current { get { return m_Enumerator.Current; } }\n            object IEnumerator.Current { get { return m_Enumerator.Current; } }\n            public bool MoveNext() { return m_Enumerator.MoveNext(); }\n\n            public void Dispose()\n            {\n                m_Node = null;\n                m_Enumerator = new Enumerator();\n            }\n\n            public IEnumerator<KeyValuePair<string, JSONNode>> GetEnumerator()\n            {\n                return new LinqEnumerator(m_Node);\n            }\n\n            public void Reset()\n            {\n                if (m_Node != null)\n                    m_Enumerator = m_Node.GetEnumerator();\n            }\n\n            IEnumerator IEnumerable.GetEnumerator()\n            {\n                return new LinqEnumerator(m_Node);\n            }\n        }\n\n        #endregion Enumerators\n\n        #region common interface\n\n        public static bool forceASCII = false; // Use Unicode by default\n        public static bool longAsString = false; // lazy creator creates a JSONString instead of JSONNumber\n        public static bool allowLineComments = true; // allow \"//\"-style comments at the end of a line\n\n        public abstract JSONNodeType Tag { get; }\n\n        public virtual JSONNode this[int aIndex] { get { return null; } set { } }\n\n        public virtual JSONNode this[string aKey] { get { return null; } set { } }\n\n        public virtual string Value { get { return \"\"; } set { } }\n\n        public virtual int Count { get { return 0; } }\n\n        public virtual bool IsNumber { get { return false; } }\n        public virtual bool IsString { get { return false; } }\n        public virtual bool IsBoolean { get { return false; } }\n        public virtual bool IsNull { get { return false; } }\n        public virtual bool IsArray { get { return false; } }\n        public virtual bool IsObject { get { return false; } }\n\n        public virtual bool Inline { get { return false; } set { } }\n\n        public virtual void Add(string aKey, JSONNode aItem)\n        {\n        }\n        public virtual void Add(JSONNode aItem)\n        {\n            Add(\"\", aItem);\n        }\n\n        public virtual JSONNode Remove(string aKey)\n        {\n            return null;\n        }\n\n        public virtual JSONNode Remove(int aIndex)\n        {\n            return null;\n        }\n\n        public virtual JSONNode Remove(JSONNode aNode)\n        {\n            return aNode;\n        }\n\n        public virtual IEnumerable<JSONNode> Children\n        {\n            get\n            {\n                yield break;\n            }\n        }\n\n        public IEnumerable<JSONNode> DeepChildren\n        {\n            get\n            {\n                foreach (var C in Children)\n                    foreach (var D in C.DeepChildren)\n                        yield return D;\n            }\n        }\n\n        public virtual bool HasKey(string aKey)\n        {\n            return false;\n        }\n\n        public virtual JSONNode GetValueOrDefault(string aKey, JSONNode aDefault)\n        {\n            return aDefault;\n        }\n\n        public override string ToString()\n        {\n            StringBuilder sb = new StringBuilder();\n            WriteToStringBuilder(sb, 0, 0, JSONTextMode.Compact);\n            return sb.ToString();\n        }\n\n        public virtual string ToString(int aIndent)\n        {\n            StringBuilder sb = new StringBuilder();\n            WriteToStringBuilder(sb, 0, aIndent, JSONTextMode.Indent);\n            return sb.ToString();\n        }\n        internal abstract void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode);\n\n        public abstract Enumerator GetEnumerator();\n        public IEnumerable<KeyValuePair<string, JSONNode>> Linq { get { return new LinqEnumerator(this); } }\n        public KeyEnumerator Keys { get { return new KeyEnumerator(GetEnumerator()); } }\n        public ValueEnumerator Values { get { return new ValueEnumerator(GetEnumerator()); } }\n\n        #endregion common interface\n\n        #region typecasting properties\n\n\n        public virtual double AsDouble\n        {\n            get\n            {\n                double v = 0.0;\n                if (double.TryParse(Value,NumberStyles.Float, CultureInfo.InvariantCulture, out v))\n                    return v;\n                return 0.0;\n            }\n            set\n            {\n                Value = value.ToString(CultureInfo.InvariantCulture);\n            }\n        }\n\n        public virtual int AsInt\n        {\n            get { return (int)AsDouble; }\n            set { AsDouble = value; }\n        }\n\n        public virtual float AsFloat\n        {\n            get { return (float)AsDouble; }\n            set { AsDouble = value; }\n        }\n\n        public virtual bool AsBool\n        {\n            get\n            {\n                bool v = false;\n                if (bool.TryParse(Value, out v))\n                    return v;\n                return !string.IsNullOrEmpty(Value);\n            }\n            set\n            {\n                Value = (value) ? \"true\" : \"false\";\n            }\n        }\n\n        public virtual long AsLong\n        {\n            get\n            {\n                long val = 0;\n                if (long.TryParse(Value, out val))\n                    return val;\n                return 0L;\n            }\n            set\n            {\n                Value = value.ToString();\n            }\n        }\n\n        public virtual JSONArray AsArray\n        {\n            get\n            {\n                return this as JSONArray;\n            }\n        }\n\n        public virtual JSONObject AsObject\n        {\n            get\n            {\n                return this as JSONObject;\n            }\n        }\n\n\n        #endregion typecasting properties\n\n        #region operators\n\n        public static implicit operator JSONNode(string s)\n        {\n            return new JSONString(s);\n        }\n        public static implicit operator string(JSONNode d)\n        {\n            return (d == null) ? null : d.Value;\n        }\n\n        public static implicit operator JSONNode(double n)\n        {\n            return new JSONNumber(n);\n        }\n        public static implicit operator double(JSONNode d)\n        {\n            return (d == null) ? 0 : d.AsDouble;\n        }\n\n        public static implicit operator JSONNode(float n)\n        {\n            return new JSONNumber(n);\n        }\n        public static implicit operator float(JSONNode d)\n        {\n            return (d == null) ? 0 : d.AsFloat;\n        }\n\n        public static implicit operator JSONNode(int n)\n        {\n            return new JSONNumber(n);\n        }\n        public static implicit operator int(JSONNode d)\n        {\n            return (d == null) ? 0 : d.AsInt;\n        }\n\n        public static implicit operator JSONNode(long n)\n        {\n            if (longAsString)\n                return new JSONString(n.ToString());\n            return new JSONNumber(n);\n        }\n        public static implicit operator long(JSONNode d)\n        {\n            return (d == null) ? 0L : d.AsLong;\n        }\n\n        public static implicit operator JSONNode(bool b)\n        {\n            return new JSONBool(b);\n        }\n        public static implicit operator bool(JSONNode d)\n        {\n            return (d == null) ? false : d.AsBool;\n        }\n\n        public static implicit operator JSONNode(KeyValuePair<string, JSONNode> aKeyValue)\n        {\n            return aKeyValue.Value;\n        }\n\n        public static bool operator ==(JSONNode a, object b)\n        {\n            if (ReferenceEquals(a, b))\n                return true;\n            bool aIsNull = a is JSONNull || ReferenceEquals(a, null) || a is JSONLazyCreator;\n            bool bIsNull = b is JSONNull || ReferenceEquals(b, null) || b is JSONLazyCreator;\n            if (aIsNull && bIsNull)\n                return true;\n            return !aIsNull && a.Equals(b);\n        }\n\n        public static bool operator !=(JSONNode a, object b)\n        {\n            return !(a == b);\n        }\n\n        public override bool Equals(object obj)\n        {\n            return ReferenceEquals(this, obj);\n        }\n\n        public override int GetHashCode()\n        {\n            return base.GetHashCode();\n        }\n\n        #endregion operators\n\n        [ThreadStatic]\n        private static StringBuilder m_EscapeBuilder;\n        internal static StringBuilder EscapeBuilder\n        {\n            get {\n                if (m_EscapeBuilder == null)\n                    m_EscapeBuilder = new StringBuilder();\n                return m_EscapeBuilder;\n            }\n        }\n        internal static string Escape(string aText)\n        {\n            var sb = EscapeBuilder;\n            sb.Length = 0;\n            if (sb.Capacity < aText.Length + aText.Length / 10)\n                sb.Capacity = aText.Length + aText.Length / 10;\n            foreach (char c in aText)\n            {\n                switch (c)\n                {\n                    case '\\\\':\n                        sb.Append(\"\\\\\\\\\");\n                        break;\n                    case '\\\"':\n                        sb.Append(\"\\\\\\\"\");\n                        break;\n                    case '\\n':\n                        sb.Append(\"\\\\n\");\n                        break;\n                    case '\\r':\n                        sb.Append(\"\\\\r\");\n                        break;\n                    case '\\t':\n                        sb.Append(\"\\\\t\");\n                        break;\n                    case '\\b':\n                        sb.Append(\"\\\\b\");\n                        break;\n                    case '\\f':\n                        sb.Append(\"\\\\f\");\n                        break;\n                    default:\n                        if (c < ' ' || (forceASCII && c > 127))\n                        {\n                            ushort val = c;\n                            sb.Append(\"\\\\u\").Append(val.ToString(\"X4\"));\n                        }\n                        else\n                            sb.Append(c);\n                        break;\n                }\n            }\n            string result = sb.ToString();\n            sb.Length = 0;\n            return result;\n        }\n\n        private static JSONNode ParseElement(string token, bool quoted)\n        {\n            if (quoted)\n                return token;\n            string tmp = token.ToLower();\n            if (tmp == \"false\" || tmp == \"true\")\n                return tmp == \"true\";\n            if (tmp == \"null\")\n                return JSONNull.CreateOrGet();\n            double val;\n            if (double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out val))\n                return val;\n            else\n                return token;\n        }\n\n        public static JSONNode Parse(string aJSON)\n        {\n            Stack<JSONNode> stack = new Stack<JSONNode>();\n            JSONNode ctx = null;\n            int i = 0;\n            StringBuilder Token = new StringBuilder();\n            string TokenName = \"\";\n            bool QuoteMode = false;\n            bool TokenIsQuoted = false;\n            while (i < aJSON.Length)\n            {\n                switch (aJSON[i])\n                {\n                    case '{':\n                        if (QuoteMode)\n                        {\n                            Token.Append(aJSON[i]);\n                            break;\n                        }\n                        stack.Push(new JSONObject());\n                        if (ctx != null)\n                        {\n                            ctx.Add(TokenName, stack.Peek());\n                        }\n                        TokenName = \"\";\n                        Token.Length = 0;\n                        ctx = stack.Peek();\n                        break;\n\n                    case '[':\n                        if (QuoteMode)\n                        {\n                            Token.Append(aJSON[i]);\n                            break;\n                        }\n\n                        stack.Push(new JSONArray());\n                        if (ctx != null)\n                        {\n                            ctx.Add(TokenName, stack.Peek());\n                        }\n                        TokenName = \"\";\n                        Token.Length = 0;\n                        ctx = stack.Peek();\n                        break;\n\n                    case '}':\n                    case ']':\n                        if (QuoteMode)\n                        {\n\n                            Token.Append(aJSON[i]);\n                            break;\n                        }\n                        if (stack.Count == 0)\n                            throw new Exception(\"JSON Parse: Too many closing brackets\");\n\n                        stack.Pop();\n                        if (Token.Length > 0 || TokenIsQuoted)\n                            ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted));\n                        TokenIsQuoted = false;\n                        TokenName = \"\";\n                        Token.Length = 0;\n                        if (stack.Count > 0)\n                            ctx = stack.Peek();\n                        break;\n\n                    case ':':\n                        if (QuoteMode)\n                        {\n                            Token.Append(aJSON[i]);\n                            break;\n                        }\n                        TokenName = Token.ToString();\n                        Token.Length = 0;\n                        TokenIsQuoted = false;\n                        break;\n\n                    case '\"':\n                        QuoteMode ^= true;\n                        TokenIsQuoted |= QuoteMode;\n                        break;\n\n                    case ',':\n                        if (QuoteMode)\n                        {\n                            Token.Append(aJSON[i]);\n                            break;\n                        }\n                        if (Token.Length > 0 || TokenIsQuoted)\n                            ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted));\n                        TokenIsQuoted = false;\n                        TokenName = \"\";\n                        Token.Length = 0;\n                        TokenIsQuoted = false;\n                        break;\n\n                    case '\\r':\n                    case '\\n':\n                        break;\n\n                    case ' ':\n                    case '\\t':\n                        if (QuoteMode)\n                            Token.Append(aJSON[i]);\n                        break;\n\n                    case '\\\\':\n                        ++i;\n                        if (QuoteMode)\n                        {\n                            char C = aJSON[i];\n                            switch (C)\n                            {\n                                case 't':\n                                    Token.Append('\\t');\n                                    break;\n                                case 'r':\n                                    Token.Append('\\r');\n                                    break;\n                                case 'n':\n                                    Token.Append('\\n');\n                                    break;\n                                case 'b':\n                                    Token.Append('\\b');\n                                    break;\n                                case 'f':\n                                    Token.Append('\\f');\n                                    break;\n                                case 'u':\n                                    {\n                                        string s = aJSON.Substring(i + 1, 4);\n                                        Token.Append((char)int.Parse(\n                                            s,\n                                            System.Globalization.NumberStyles.AllowHexSpecifier));\n                                        i += 4;\n                                        break;\n                                    }\n                                default:\n                                    Token.Append(C);\n                                    break;\n                            }\n                        }\n                        break;\n                    case '/':\n                        if (allowLineComments && !QuoteMode && i + 1 < aJSON.Length && aJSON[i+1] == '/')\n                        {\n                            while (++i < aJSON.Length && aJSON[i] != '\\n' && aJSON[i] != '\\r') ;\n                            break;\n                        }\n                        Token.Append(aJSON[i]);\n                        break;\n                    case '\\uFEFF': // remove / ignore BOM (Byte Order Mark)\n                        break;\n\n                    default:\n                        Token.Append(aJSON[i]);\n                        break;\n                }\n                ++i;\n            }\n            if (QuoteMode)\n            {\n                throw new Exception(\"JSON Parse: Quotation marks seems to be messed up.\");\n            }\n            if (ctx == null)\n                return ParseElement(Token.ToString(), TokenIsQuoted);\n            return ctx;\n        }\n\n    }\n    // End of JSONNode\n\n    public partial class JSONArray : JSONNode\n    {\n        private List<JSONNode> m_List = new List<JSONNode>();\n        private bool inline = false;\n        public override bool Inline\n        {\n            get { return inline; }\n            set { inline = value; }\n        }\n\n        public override JSONNodeType Tag { get { return JSONNodeType.Array; } }\n        public override bool IsArray { get { return true; } }\n        public override Enumerator GetEnumerator() { return new Enumerator(m_List.GetEnumerator()); }\n\n        public override JSONNode this[int aIndex]\n        {\n            get\n            {\n                if (aIndex < 0 || aIndex >= m_List.Count)\n                    return new JSONLazyCreator(this);\n                return m_List[aIndex];\n            }\n            set\n            {\n                if (value == null)\n                    value = JSONNull.CreateOrGet();\n                if (aIndex < 0 || aIndex >= m_List.Count)\n                    m_List.Add(value);\n                else\n                    m_List[aIndex] = value;\n            }\n        }\n\n        public override JSONNode this[string aKey]\n        {\n            get { return new JSONLazyCreator(this); }\n            set\n            {\n                if (value == null)\n                    value = JSONNull.CreateOrGet();\n                m_List.Add(value);\n            }\n        }\n\n        public override int Count\n        {\n            get { return m_List.Count; }\n        }\n\n        public override void Add(string aKey, JSONNode aItem)\n        {\n            if (aItem == null)\n                aItem = JSONNull.CreateOrGet();\n            m_List.Add(aItem);\n        }\n\n        public override JSONNode Remove(int aIndex)\n        {\n            if (aIndex < 0 || aIndex >= m_List.Count)\n                return null;\n            JSONNode tmp = m_List[aIndex];\n            m_List.RemoveAt(aIndex);\n            return tmp;\n        }\n\n        public override JSONNode Remove(JSONNode aNode)\n        {\n            m_List.Remove(aNode);\n            return aNode;\n        }\n\n        public override IEnumerable<JSONNode> Children\n        {\n            get\n            {\n                foreach (JSONNode N in m_List)\n                    yield return N;\n            }\n        }\n\n\n        internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)\n        {\n            aSB.Append('[');\n            int count = m_List.Count;\n            if (inline)\n                aMode = JSONTextMode.Compact;\n            for (int i = 0; i < count; i++)\n            {\n                if (i > 0)\n                    aSB.Append(',');\n                if (aMode == JSONTextMode.Indent)\n                    aSB.AppendLine();\n\n                if (aMode == JSONTextMode.Indent)\n                    aSB.Append(' ', aIndent + aIndentInc);\n                m_List[i].WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode);\n            }\n            if (aMode == JSONTextMode.Indent)\n                aSB.AppendLine().Append(' ', aIndent);\n            aSB.Append(']');\n        }\n    }\n    // End of JSONArray\n\n    public partial class JSONObject : JSONNode\n    {\n        public Dictionary<string, JSONNode> m_Dict = new Dictionary<string, JSONNode>();\n\n        private bool inline = false;\n        public override bool Inline\n        {\n            get { return inline; }\n            set { inline = value; }\n        }\n\n        public override JSONNodeType Tag { get { return JSONNodeType.Object; } }\n        public override bool IsObject { get { return true; } }\n\n        public override Enumerator GetEnumerator() { return new Enumerator(m_Dict.GetEnumerator()); }\n\n\n        public override JSONNode this[string aKey]\n        {\n            get\n            {\n                if (m_Dict.ContainsKey(aKey))\n                    return m_Dict[aKey];\n                else\n                    return new JSONLazyCreator(this, aKey);\n            }\n            set\n            {\n                if (value == null)\n                    value = JSONNull.CreateOrGet();\n                if (m_Dict.ContainsKey(aKey))\n                    m_Dict[aKey] = value;\n                else\n                    m_Dict.Add(aKey, value);\n            }\n        }\n\n        public override JSONNode this[int aIndex]\n        {\n            get\n            {\n                if (aIndex < 0 || aIndex >= m_Dict.Count)\n                    return null;\n                return m_Dict.ElementAt(aIndex).Value;\n            }\n            set\n            {\n                if (value == null)\n                    value = JSONNull.CreateOrGet();\n                if (aIndex < 0 || aIndex >= m_Dict.Count)\n                    return;\n                string key = m_Dict.ElementAt(aIndex).Key;\n                m_Dict[key] = value;\n            }\n        }\n\n        public override int Count\n        {\n            get { return m_Dict.Count; }\n        }\n\n        public override void Add(string aKey, JSONNode aItem)\n        {\n            if (aItem == null)\n                aItem = JSONNull.CreateOrGet();\n\n            if (aKey != null)\n            {\n                if (m_Dict.ContainsKey(aKey))\n                    m_Dict[aKey] = aItem;\n                else\n                    m_Dict.Add(aKey, aItem);\n            }\n            else\n                m_Dict.Add(Guid.NewGuid().ToString(), aItem);\n        }\n\n        public override JSONNode Remove(string aKey)\n        {\n            if (!m_Dict.ContainsKey(aKey))\n                return null;\n            JSONNode tmp = m_Dict[aKey];\n            m_Dict.Remove(aKey);\n            return tmp;\n        }\n\n        public override JSONNode Remove(int aIndex)\n        {\n            if (aIndex < 0 || aIndex >= m_Dict.Count)\n                return null;\n            var item = m_Dict.ElementAt(aIndex);\n            m_Dict.Remove(item.Key);\n            return item.Value;\n        }\n\n        public override JSONNode Remove(JSONNode aNode)\n        {\n            try\n            {\n                var item = m_Dict.Where(k => k.Value == aNode).First();\n                m_Dict.Remove(item.Key);\n                return aNode;\n            }\n            catch\n            {\n                return null;\n            }\n        }\n\n        public override bool HasKey(string aKey)\n        {\n            return m_Dict.ContainsKey(aKey);\n        }\n\n        public override JSONNode GetValueOrDefault(string aKey, JSONNode aDefault)\n        {\n            JSONNode res;\n            if (m_Dict.TryGetValue(aKey, out res))\n                return res;\n            return aDefault;\n        }\n\n        public override IEnumerable<JSONNode> Children\n        {\n            get\n            {\n                foreach (KeyValuePair<string, JSONNode> N in m_Dict)\n                    yield return N.Value;\n            }\n        }\n\n        internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)\n        {\n            aSB.Append('{');\n            bool first = true;\n            if (inline)\n                aMode = JSONTextMode.Compact;\n            foreach (var k in m_Dict)\n            {\n                if (!first)\n                    aSB.Append(',');\n                first = false;\n                if (aMode == JSONTextMode.Indent)\n                    aSB.AppendLine();\n                if (aMode == JSONTextMode.Indent)\n                    aSB.Append(' ', aIndent + aIndentInc);\n                aSB.Append('\\\"').Append(Escape(k.Key)).Append('\\\"');\n                if (aMode == JSONTextMode.Compact)\n                    aSB.Append(':');\n                else\n                    aSB.Append(\" : \");\n                k.Value.WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode);\n            }\n            if (aMode == JSONTextMode.Indent)\n                aSB.AppendLine().Append(' ', aIndent);\n            aSB.Append('}');\n        }\n\n    }\n    // End of JSONObject\n\n    public partial class JSONString : JSONNode\n    {\n        private string m_Data;\n\n        public override JSONNodeType Tag { get { return JSONNodeType.String; } }\n        public override bool IsString { get { return true; } }\n\n        public override Enumerator GetEnumerator() { return new Enumerator(); }\n\n\n        public override string Value\n        {\n            get { return m_Data; }\n            set\n            {\n                m_Data = value;\n            }\n        }\n\n        public JSONString(string aData)\n        {\n            m_Data = aData;\n        }\n\n        internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)\n        {\n            aSB.Append('\\\"').Append(Escape(m_Data)).Append('\\\"');\n        }\n        public override bool Equals(object obj)\n        {\n            if (base.Equals(obj))\n                return true;\n            string s = obj as string;\n            if (s != null)\n                return m_Data == s;\n            JSONString s2 = obj as JSONString;\n            if (s2 != null)\n                return m_Data == s2.m_Data;\n            return false;\n        }\n        public override int GetHashCode()\n        {\n            return m_Data.GetHashCode();\n        }\n    }\n    // End of JSONString\n\n    public partial class JSONNumber : JSONNode\n    {\n        private double m_Data;\n\n        public override JSONNodeType Tag { get { return JSONNodeType.Number; } }\n        public override bool IsNumber { get { return true; } }\n        public override Enumerator GetEnumerator() { return new Enumerator(); }\n\n        public override string Value\n        {\n            get { return m_Data.ToString(CultureInfo.InvariantCulture); }\n            set\n            {\n                double v;\n                if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out v))\n                    m_Data = v;\n            }\n        }\n\n        public override double AsDouble\n        {\n            get { return m_Data; }\n            set { m_Data = value; }\n        }\n        public override long AsLong\n        {\n            get { return (long)m_Data; }\n            set { m_Data = value; }\n        }\n\n        public JSONNumber(double aData)\n        {\n            m_Data = aData;\n        }\n\n        public JSONNumber(string aData)\n        {\n            Value = aData;\n        }\n\n        internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)\n        {\n            aSB.Append(Value);\n        }\n        private static bool IsNumeric(object value)\n        {\n            return value is int || value is uint\n                || value is float || value is double\n                || value is decimal\n                || value is long || value is ulong\n                || value is short || value is ushort\n                || value is sbyte || value is byte;\n        }\n        public override bool Equals(object obj)\n        {\n            if (obj == null)\n                return false;\n            if (base.Equals(obj))\n                return true;\n            JSONNumber s2 = obj as JSONNumber;\n            if (s2 != null)\n                return m_Data == s2.m_Data;\n            if (IsNumeric(obj))\n                return Convert.ToDouble(obj) == m_Data;\n            return false;\n        }\n        public override int GetHashCode()\n        {\n            return m_Data.GetHashCode();\n        }\n    }\n    // End of JSONNumber\n\n    public partial class JSONBool : JSONNode\n    {\n        private bool m_Data;\n\n        public override JSONNodeType Tag { get { return JSONNodeType.Boolean; } }\n        public override bool IsBoolean { get { return true; } }\n        public override Enumerator GetEnumerator() { return new Enumerator(); }\n\n        public override string Value\n        {\n            get { return m_Data.ToString(); }\n            set\n            {\n                bool v;\n                if (bool.TryParse(value, out v))\n                    m_Data = v;\n            }\n        }\n        public override bool AsBool\n        {\n            get { return m_Data; }\n            set { m_Data = value; }\n        }\n\n        public JSONBool(bool aData)\n        {\n            m_Data = aData;\n        }\n\n        public JSONBool(string aData)\n        {\n            Value = aData;\n        }\n\n        internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)\n        {\n            aSB.Append((m_Data) ? \"true\" : \"false\");\n        }\n        public override bool Equals(object obj)\n        {\n            if (obj == null)\n                return false;\n            if (obj is bool)\n                return m_Data == (bool)obj;\n            return false;\n        }\n        public override int GetHashCode()\n        {\n            return m_Data.GetHashCode();\n        }\n    }\n    // End of JSONBool\n\n    public partial class JSONNull : JSONNode\n    {\n        static JSONNull m_StaticInstance = new JSONNull();\n        public static bool reuseSameInstance = true;\n        public static JSONNull CreateOrGet()\n        {\n            if (reuseSameInstance)\n                return m_StaticInstance;\n            return new JSONNull();\n        }\n        private JSONNull() { }\n\n        public override JSONNodeType Tag { get { return JSONNodeType.NullValue; } }\n        public override bool IsNull { get { return true; } }\n        public override Enumerator GetEnumerator() { return new Enumerator(); }\n\n        public override string Value\n        {\n            get { return \"null\"; }\n            set { }\n        }\n        public override bool AsBool\n        {\n            get { return false; }\n            set { }\n        }\n\n        public override bool Equals(object obj)\n        {\n            if (object.ReferenceEquals(this, obj))\n                return true;\n            return (obj is JSONNull);\n        }\n        public override int GetHashCode()\n        {\n            return 0;\n        }\n\n        internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)\n        {\n            aSB.Append(\"null\");\n        }\n    }\n    // End of JSONNull\n\n    internal partial class JSONLazyCreator : JSONNode\n    {\n        private JSONNode m_Node = null;\n        private string m_Key = null;\n        public override JSONNodeType Tag { get { return JSONNodeType.None; } }\n        public override Enumerator GetEnumerator() { return new Enumerator(); }\n\n        public JSONLazyCreator(JSONNode aNode)\n        {\n            m_Node = aNode;\n            m_Key = null;\n        }\n\n        public JSONLazyCreator(JSONNode aNode, string aKey)\n        {\n            m_Node = aNode;\n            m_Key = aKey;\n        }\n\n        private T Set<T>(T aVal) where T : JSONNode\n        {\n            if (m_Key == null)\n                m_Node.Add(aVal);\n            else\n                m_Node.Add(m_Key, aVal);\n            m_Node = null; // Be GC friendly.\n            return aVal;\n        }\n\n        public override JSONNode this[int aIndex]\n        {\n            get { return new JSONLazyCreator(this); }\n            set { Set(new JSONArray()).Add(value); }\n        }\n\n        public override JSONNode this[string aKey]\n        {\n            get { return new JSONLazyCreator(this, aKey); }\n            set { Set(new JSONObject()).Add(aKey, value); }\n        }\n\n        public override void Add(JSONNode aItem)\n        {\n            Set(new JSONArray()).Add(aItem);\n        }\n\n        public override void Add(string aKey, JSONNode aItem)\n        {\n            Set(new JSONObject()).Add(aKey, aItem);\n        }\n\n        public static bool operator ==(JSONLazyCreator a, object b)\n        {\n            if (b == null)\n                return true;\n            return System.Object.ReferenceEquals(a, b);\n        }\n\n        public static bool operator !=(JSONLazyCreator a, object b)\n        {\n            return !(a == b);\n        }\n\n        public override bool Equals(object obj)\n        {\n            if (obj == null)\n                return true;\n            return System.Object.ReferenceEquals(this, obj);\n        }\n\n        public override int GetHashCode()\n        {\n            return 0;\n        }\n\n        public override int AsInt\n        {\n            get { Set(new JSONNumber(0)); return 0; }\n            set { Set(new JSONNumber(value)); }\n        }\n\n        public override float AsFloat\n        {\n            get { Set(new JSONNumber(0.0f)); return 0.0f; }\n            set { Set(new JSONNumber(value)); }\n        }\n\n        public override double AsDouble\n        {\n            get { Set(new JSONNumber(0.0)); return 0.0; }\n            set { Set(new JSONNumber(value)); }\n        }\n\n        public override long AsLong\n        {\n            get\n            {\n                if (longAsString)\n                    Set(new JSONString(\"0\"));\n                else\n                    Set(new JSONNumber(0.0));\n                return 0L;\n            }\n            set\n            {\n                if (longAsString)\n                    Set(new JSONString(value.ToString()));\n                else\n                    Set(new JSONNumber(value));\n            }\n        }\n\n        public override bool AsBool\n        {\n            get { Set(new JSONBool(false)); return false; }\n            set { Set(new JSONBool(value)); }\n        }\n\n        public override JSONArray AsArray\n        {\n            get { return Set(new JSONArray()); }\n        }\n\n        public override JSONObject AsObject\n        {\n            get { return Set(new JSONObject()); }\n        }\n        internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)\n        {\n            aSB.Append(\"null\");\n        }\n    }\n    // End of JSONLazyCreator\n\n    public static class JSON\n    {\n        public static JSONNode Parse(string aJSON)\n        {\n            return JSONNode.Parse(aJSON);\n        }\n    }\n}\n"
  },
  {
    "path": "csharp/graph.JSON",
    "content": "{\"last_node_id\":15,\"last_link_id\":26,\"nodes\":[{\"id\":5,\"type\":\"graph/output\",\"pos\":[1265,331],\"size\":[180,60],\"flags\":{\"collapsed\":false},\"order\":4,\"mode\":0,\"inputs\":[{\"name\":\"\",\"type\":\"number\",\"link\":24}],\"properties\":{\"name\":\"output\",\"type\":\"number\"}},{\"id\":13,\"type\":\"basic/watch\",\"pos\":[1228,213],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"order\":5,\"mode\":0,\"inputs\":[{\"name\":\"value\",\"type\":0,\"link\":25,\"label\":\"0.000\"}],\"properties\":{}},{\"id\":9,\"type\":\"math/condition\",\"pos\":[754,261],\"size\":[180,61],\"flags\":{},\"order\":2,\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":9},{\"name\":\"B\",\"type\":\"number\",\"link\":10}],\"outputs\":[{\"name\":\"true\",\"type\":\"boolean\",\"links\":[21]},{\"name\":\"false\",\"type\":\"boolean\",\"links\":null}],\"properties\":{\"A\":6.957165000028908,\"B\":4,\"OP\":\"<\"}},{\"id\":7,\"type\":\"basic/time\",\"pos\":[406,262],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"order\":0,\"mode\":0,\"outputs\":[{\"name\":\"in ms\",\"type\":\"number\",\"links\":null},{\"name\":\"in sec\",\"type\":\"number\",\"links\":[9,22]}],\"properties\":{}},{\"id\":10,\"type\":\"basic/const\",\"pos\":[298,560],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"order\":1,\"mode\":0,\"outputs\":[{\"name\":\"value\",\"type\":\"number\",\"links\":[10,23],\"label\":\"4.000\"}],\"properties\":{\"value\":4}},{\"id\":15,\"type\":\"math/gate\",\"pos\":[961,483],\"size\":{\"0\":140,\"1\":66},\"flags\":{},\"order\":3,\"mode\":0,\"inputs\":[{\"name\":\"v\",\"type\":\"boolean\",\"link\":21},{\"name\":\"A\",\"type\":0,\"link\":22},{\"name\":\"B\",\"type\":0,\"link\":23}],\"outputs\":[{\"name\":\"out\",\"links\":[24,25]}],\"properties\":{}}],\"links\":[[9,7,1,9,0,\"number\"],[10,10,0,9,1,\"number\"],[21,9,0,15,0,\"boolean\"],[22,7,1,15,1,0],[23,10,0,15,2,0],[24,15,0,5,0,\"number\"],[25,15,0,13,0,0]],\"groups\":[],\"config\":{},\"version\":0.4}"
  },
  {
    "path": "csharp/readme.md",
    "content": "# C SHARP\n\nThis code allows to execute a subset of nodes of LiteGraph directly in Unity.\n\nStill a work in progress.\n"
  },
  {
    "path": "css/litegraph-editor.css",
    "content": ".litegraph-editor {\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n\n    background-color: #333;\n    color: #eee;\n    font: 14px Tahoma;\n\n    position: relative;\n}\n\n.litegraph-editor h1 {\n    font-family: \"Metro Light\", Tahoma;\n    color: #ddd;\n    font-size: 28px;\n    padding-left: 10px;\n    /*text-shadow: 0 1px 1px #333, 0 -1px 1px #777;*/\n    margin: 0;\n    font-weight: normal;\n}\n\n.litegraph-editor h1 span {\n    font-family: \"Arial\";\n    font-size: 14px;\n    font-weight: normal;\n    color: #aaa;\n}\n\n.litegraph-editor h2 {\n    font-family: \"Metro Light\";\n    padding: 5px;\n    margin-left: 10px;\n}\n\n.litegraph-editor * {\n    box-sizing: border-box;\n    -moz-box-sizing: border-box;\n}\n\n.litegraph-editor .content {\n    position: relative;\n    width: 100%;\n    height: calc(100% - 80px);\n    background-color: #1a1a1a;\n}\n\n.litegraph-editor .header,\n.litegraph-editor .footer {\n    position: relative;\n    height: 40px;\n    background-color: #333;\n    /*border-radius: 10px 10px 0 0;*/\n}\n\n.litegraph-editor .tools,\n.litegraph-editor .tools-left,\n.litegraph-editor .tools-right {\n    position: absolute;\n    top: 2px;\n    right: 0px;\n    vertical-align: top;\n\n    margin: 2px 5px 0 0px;\n}\n\n.litegraph-editor .tools-left {\n    right: auto;\n    left: 4px;\n}\n\n.litegraph-editor .footer {\n    height: 40px;\n    position: relative;\n    /*border-radius: 0 0 10px 10px;*/\n}\n\n.litegraph-editor .miniwindow {\n    background-color: #333;\n    border: 1px solid #111;\n}\n\n.litegraph-editor .miniwindow .corner-button {\n    position: absolute;\n    top: 2px;\n    right: 2px;\n    font-family: \"Tahoma\";\n    font-size: 14px;\n    color: #aaa;\n    cursor: pointer;\n}\n\n/* BUTTONS **********************/\n\n.litegraph-editor .btn {\n    /*font-family: \"Metro Light\";*/\n    color: #ccc;\n    font-size: 20px;\n    min-width: 30px;\n    /*border-radius: 0.3em;*/\n    border: 0 solid #666;\n    background-color: #3f3f3f;\n    /*box-shadow: 0 0 3px black;*/\n    padding: 4px 10px;\n    cursor: pointer;\n    transition: all 1s;\n    -moz-transition: all 1s;\n    -webkit-transition: all 0.4s;\n}\n\n.litegraph-editor button:hover {\n    background-color: #999;\n    color: #fff;\n    transition: all 1s;\n    -moz-transition: all 1s;\n    -webkit-transition: all 0.4s;\n}\n\n.litegraph-editor button:active {\n    background-color: white;\n}\n\n.litegraph-editor button.fixed {\n    position: absolute;\n    top: 5px;\n    right: 5px;\n    font-size: 1.2em;\n}\n\n.litegraph-editor button img {\n    margin: -4px;\n    vertical-align: top;\n    opacity: 0.8;\n    transition: all 1s;\n}\n\n.litegraph-editor button:hover img {\n    opacity: 1;\n}\n\n.litegraph-editor .header button {\n    height: 32px;\n    vertical-align: top;\n}\n\n.litegraph-editor .footer button {\n    /*font-size: 16px;*/\n}\n\n.litegraph-editor .toolbar-widget {\n    display: inline-block;\n}\n\n.litegraph-editor .editor-area {\n    width: 100%;\n    height: 100%;\n}\n\n/* METER *********************/\n\n.litegraph-editor .loadmeter {\n    font-family: \"Tahoma\";\n    color: #aaa;\n    font-size: 12px;\n    border-radius: 2px;\n    width: 130px;\n    vertical-align: top;\n}\n\n.litegraph-editor .strong {\n    vertical-align: top;\n    padding: 3px;\n    width: 30px;\n    display: inline-block;\n    line-height: 8px;\n}\n\n.litegraph-editor .cpuload .bgload,\n.litegraph-editor .gpuload .bgload {\n    display: inline-block;\n    width: 90px;\n    height: 15px;\n    background-image: url(\"../editor/imgs/load-progress-empty.png\");\n}\n\n.litegraph-editor .cpuload .fgload,\n.litegraph-editor .gpuload .fgload {\n    display: inline-block;\n    width: 4px;\n    height: 15px;\n    max-width: 90px;\n    background-image: url(\"../editor/imgs/load-progress-full.png\");\n}\n\n.litegraph-editor textarea.code, .litegraph-editor div.code {\n\theight: 100%;\n\twidth: 100%;\n\tbackground-color: black;\n\tpadding: 4px;\n\tfont: 16px monospace;\n\toverflow: auto;\n\tresize: none;\n\toutline: none;\n    color: #DDD;\n}\n\n.litegraph-editor .codeflask {\n\tbackground-color: #2a2a2a;\n}\n\n.litegraph-editor .codeflask textarea {\n\topacity: 0;\n}"
  },
  {
    "path": "css/litegraph.css",
    "content": "/* this CSS contains only the basic CSS needed to run the app and use it */\n\n.lgraphcanvas {\n    /*cursor: crosshair;*/\n    user-select: none;\n    -moz-user-select: none;\n    -webkit-user-select: none;\n\toutline: none;\n    font-family: Tahoma, sans-serif;\n}\n\n.lgraphcanvas * {\n    box-sizing: border-box;\n}\n\n.litegraph.litecontextmenu {\n    font-family: Tahoma, sans-serif;\n    position: fixed;\n    top: 100px;\n    left: 100px;\n    min-width: 100px;\n    color: #aaf;\n    padding: 0;\n    box-shadow: 0 0 10px black !important;\n    background-color: #2e2e2e !important;\n\tz-index: 10;\n}\n\n.litegraph.litecontextmenu.dark {\n    background-color: #000 !important;\n}\n\n.litegraph.litecontextmenu .litemenu-title img {\n    margin-top: 2px;\n    margin-left: 2px;\n    margin-right: 4px;\n}\n\n.litegraph.litecontextmenu .litemenu-entry {\n    margin: 2px;\n    padding: 2px;\n}\n\n.litegraph.litecontextmenu .litemenu-entry.submenu {\n    background-color: #2e2e2e !important;\n}\n\n.litegraph.litecontextmenu.dark .litemenu-entry.submenu {\n    background-color: #000 !important;\n}\n\n.litegraph .litemenubar ul {\n    font-family: Tahoma, sans-serif;\n    margin: 0;\n    padding: 0;\n}\n\n.litegraph .litemenubar li {\n    font-size: 14px;\n    color: #999;\n    display: inline-block;\n    min-width: 50px;\n    padding-left: 10px;\n    padding-right: 10px;\n    user-select: none;\n    -moz-user-select: none;\n    -webkit-user-select: none;\n    cursor: pointer;\n}\n\n.litegraph .litemenubar li:hover {\n    background-color: #777;\n    color: #eee;\n}\n\n.litegraph .litegraph .litemenubar-panel {\n    position: absolute;\n    top: 5px;\n    left: 5px;\n    min-width: 100px;\n    background-color: #444;\n    box-shadow: 0 0 3px black;\n    padding: 4px;\n    border-bottom: 2px solid #aaf;\n    z-index: 10;\n}\n\n.litegraph .litemenu-entry,\n.litemenu-title {\n    font-size: 12px;\n    color: #aaa;\n    padding: 0 0 0 4px;\n    margin: 2px;\n    padding-left: 2px;\n    -moz-user-select: none;\n    -webkit-user-select: none;\n    user-select: none;\n    cursor: pointer;\n}\n\n.litegraph .litemenu-entry .icon {\n    display: inline-block;\n    width: 12px;\n    height: 12px;\n    margin: 2px;\n    vertical-align: top;\n}\n\n.litegraph .litemenu-entry.checked .icon {\n    background-color: #aaf;\n}\n\n.litegraph .litemenu-entry .more {\n    float: right;\n    padding-right: 5px;\n}\n\n.litegraph .litemenu-entry.disabled {\n    opacity: 0.5;\n    cursor: default;\n}\n\n.litegraph .litemenu-entry.separator {\n    display: block;\n    border-top: 1px solid #333;\n    border-bottom: 1px solid #666;\n    width: 100%;\n    height: 0px;\n    margin: 3px 0 2px 0;\n    background-color: transparent;\n    padding: 0 !important;\n    cursor: default !important;\n}\n\n.litegraph .litemenu-entry.has_submenu {\n    border-right: 2px solid cyan;\n}\n\n.litegraph .litemenu-title {\n    color: #dde;\n    background-color: #111;\n    margin: 0;\n    padding: 2px;\n    cursor: default;\n}\n\n.litegraph .litemenu-entry:hover:not(.disabled):not(.separator) {\n    background-color: #444 !important;\n    color: #eee;\n    transition: all 0.2s;\n}\n\n.litegraph .litemenu-entry .property_name {\n    display: inline-block;\n    text-align: left;\n    min-width: 80px;\n    min-height: 1.2em;\n}\n\n.litegraph .litemenu-entry .property_value {\n    display: inline-block;\n    background-color: rgba(0, 0, 0, 0.5);\n    text-align: right;\n    min-width: 80px;\n    min-height: 1.2em;\n    vertical-align: middle;\n    padding-right: 10px;\n}\n\n.litegraph.litesearchbox {\n    font-family: Tahoma, sans-serif;\n    position: absolute;\n    background-color: rgba(0, 0, 0, 0.5);\n    padding-top: 4px;\n}\n\n.litegraph.litesearchbox input,\n.litegraph.litesearchbox select {\n    margin-top: 3px;\n    min-width: 60px;\n    min-height: 1.5em;\n    background-color: black;\n    border: 0;\n    color: white;\n    padding-left: 10px;\n    margin-right: 5px;\n}\n\n.litegraph.litesearchbox .name {\n    display: inline-block;\n    min-width: 60px;\n    min-height: 1.5em;\n    padding-left: 10px;\n}\n\n.litegraph.litesearchbox .helper {\n    overflow: auto;\n    max-height: 200px;\n    margin-top: 2px;\n}\n\n.litegraph.lite-search-item {\n    font-family: Tahoma, sans-serif;\n    background-color: rgba(0, 0, 0, 0.5);\n    color: white;\n    padding-top: 2px;\n}\n\n.litegraph.lite-search-item.not_in_filter{\n    /*background-color: rgba(50, 50, 50, 0.5);*/\n    /*color: #999;*/\n    color: #B99;\n    font-style: italic;\n}\n\n.litegraph.lite-search-item.generic_type{\n    /*background-color: rgba(50, 50, 50, 0.5);*/\n    /*color: #DD9;*/\n    color: #999;\n    font-style: italic;\n}\n\n.litegraph.lite-search-item:hover,\n.litegraph.lite-search-item.selected {\n    cursor: pointer;\n    background-color: white;\n    color: black;\n}\n\n/* DIALOGs ******/\n\n.litegraph .dialog {\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    margin-top: -150px;\n    margin-left: -200px;\n\n    background-color: #2A2A2A;\n\n    min-width: 400px;\n    min-height: 200px;\n\tbox-shadow: 0 0 4px #111;\n    border-radius: 6px;\n}\n\n.litegraph .dialog.settings {\n\tleft: 10px;\n\ttop: 10px;\n\theight: calc( 100% - 20px );\n\tmargin: auto;\n    max-width: 50%;\n}\n\n.litegraph .dialog.centered {\n    top: 50px;\n    left: 50%;\n    position: absolute;\n    transform: translateX(-50%);\n    min-width: 600px;\n    min-height: 300px;\n    height: calc( 100% - 100px );\n\tmargin: auto;\n}\n\n.litegraph .dialog .close {\n    float: right;\n\tmargin: 4px;\n\tmargin-right: 10px;\n\tcursor: pointer;\n\tfont-size: 1.4em;\n}\n\n.litegraph .dialog .close:hover {\n\tcolor: white;\n}\n\n.litegraph .dialog .dialog-header {\n\tcolor: #AAA;\n\tborder-bottom: 1px solid #161616;\n}\n\n.litegraph .dialog .dialog-header { height: 40px; }\n.litegraph .dialog .dialog-footer { height: 50px; padding: 10px; border-top: 1px solid #1a1a1a;}\n\n.litegraph .dialog .dialog-header .dialog-title {\n    font: 20px \"Arial\";\n    margin: 4px;\n    padding: 4px 10px;\n    display: inline-block;\n}\n\n.litegraph .dialog .dialog-content, .litegraph .dialog .dialog-alt-content {\n    height: calc(100% - 90px);\n    width: 100%;\n\tmin-height: 100px;\n    display: inline-block;\n\tcolor: #AAA;\n    /*background-color: black;*/\n    overflow: auto;\n}\n\n.litegraph .dialog .dialog-content h3 {\n\tmargin: 10px;\n}\n\n.litegraph .dialog .dialog-content .connections {\n\tflex-direction: row;\n}\n\n.litegraph .dialog .dialog-content .connections .connections_side {\n\twidth: calc(50% - 5px);\n\tmin-height: 100px;\n\tbackground-color: black;\n\tdisplay: flex;\n}\n\n.litegraph .dialog .node_type {\n\tfont-size: 1.2em;\n\tdisplay: block;\n\tmargin: 10px;\n}\n\n.litegraph .dialog .node_desc {\n\topacity: 0.5;\n\tdisplay: block;\n\tmargin: 10px;\n}\n\n.litegraph .dialog .separator {\n\tdisplay: block;\n\twidth: calc( 100% - 4px );\n\theight: 1px;\n\tborder-top: 1px solid #000;\n\tborder-bottom: 1px solid #333;\n\tmargin: 10px 2px;\n\tpadding: 0;\n}\n\n.litegraph .dialog .property {\n\tmargin-bottom: 2px;\n\tpadding: 4px;\n}\n\n.litegraph .dialog .property:hover {\n\tbackground: #545454;\n}\n\n.litegraph .dialog .property_name {\n\tcolor: #737373;\n    display: inline-block;\n    text-align: left;\n    vertical-align: top;\n    width: 160px;\n\tpadding-left: 4px;\n\toverflow: hidden;\n    margin-right: 6px;\n}\n\n.litegraph .dialog .property:hover .property_name {\n    color: white;\n}\n\n.litegraph .dialog .property_value {\n    display: inline-block;\n    text-align: right;\n\tcolor: #AAA;\n\tbackground-color: #1A1A1A;\n    /*width: calc( 100% - 122px );*/\n    max-width: calc( 100% - 162px );\n    min-width: 200px;\n\tmax-height: 300px;\n    min-height: 20px;\n    padding: 4px;\n\tpadding-right: 12px;\n\toverflow: hidden;\n\tcursor: pointer;\n\tborder-radius: 3px;\n}\n\n.litegraph .dialog .property_value:hover {\n\tcolor: white;\n}\n\n.litegraph .dialog .property.boolean .property_value {\n\tpadding-right: 30px;\n    color: #A88;\n    /*width: auto;\n    float: right;*/\n}\n\n.litegraph .dialog .property.boolean.bool-on .property_name{\n    color: #8A8;\n}\n.litegraph .dialog .property.boolean.bool-on .property_value{\n    color: #8A8;\n}\n\n.litegraph .dialog .btn {\n\tborder: 0;\n\tborder-radius: 4px;\n    padding: 4px 20px;\n    margin-left: 0px;\n    background-color: #060606;\n    color: #8e8e8e;\n}\n\n.litegraph .dialog .btn:hover {\n    background-color: #111;\n    color: #FFF;\n}\n\n.litegraph .dialog .btn.delete:hover {\n    background-color: #F33;\n    color: black;\n}\n\n.litegraph .subgraph_property {\n\tpadding: 4px;\n}\n\n.litegraph .subgraph_property:hover {\n\tbackground-color: #333;\n}\n\n.litegraph .subgraph_property.extra {\n    margin-top: 8px;\n}\n\n.litegraph .subgraph_property span.name {\n\tfont-size: 1.3em;\n\tpadding-left: 4px;\n}\n\n.litegraph .subgraph_property span.type {\n\topacity: 0.5;\n\tmargin-right: 20px;\n\tpadding-left: 4px;\n}\n\n.litegraph .subgraph_property span.label {\n\tdisplay: inline-block;\n\twidth: 60px;\n\tpadding:  0px 10px;\n}\n\n.litegraph .subgraph_property input {\n\twidth: 140px;\n\tcolor: #999;\n\tbackground-color: #1A1A1A;\n\tborder-radius: 4px;\n\tborder: 0;\n\tmargin-right: 10px;\n\tpadding: 4px;\n\tpadding-left: 10px;\n}\n\n.litegraph .subgraph_property button {\n\tbackground-color: #1c1c1c;\n\tcolor: #aaa;\n\tborder: 0;\n\tborder-radius: 2px;\n\tpadding: 4px 10px;\n\tcursor: pointer;\n}\n\n.litegraph .subgraph_property.extra {\n\tcolor: #ccc;\n}\n\n.litegraph .subgraph_property.extra input {\n\tbackground-color: #111;\n}\n\n.litegraph .bullet_icon {\n\tmargin-left: 10px;\n\tborder-radius: 10px;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #666;\n\tdisplay: inline-block;\n\tmargin-top: 2px;\n\tmargin-right: 4px;\n    transition: background-color 0.1s ease 0s;\n    -moz-transition: background-color 0.1s ease 0s;\n}\n\n.litegraph .bullet_icon:hover {\n\tbackground-color: #698;\n\tcursor: pointer;\n} \n\n/* OLD */\n\n.graphcontextmenu {\n    padding: 4px;\n    min-width: 100px;\n}\n\n.graphcontextmenu-title {\n    color: #dde;\n    background-color: #222;\n    margin: 0;\n    padding: 2px;\n    cursor: default;\n}\n\n.graphmenu-entry {\n    box-sizing: border-box;\n    margin: 2px;\n    padding-left: 20px;\n    user-select: none;\n    -moz-user-select: none;\n    -webkit-user-select: none;\n    transition: all linear 0.3s;\n}\n\n.graphmenu-entry.event,\n.litemenu-entry.event {\n    border-left: 8px solid orange;\n    padding-left: 12px;\n}\n\n.graphmenu-entry.disabled {\n    opacity: 0.3;\n}\n\n.graphmenu-entry.submenu {\n    border-right: 2px solid #eee;\n}\n\n.graphmenu-entry:hover {\n    background-color: #555;\n}\n\n.graphmenu-entry.separator {\n    background-color: #111;\n    border-bottom: 1px solid #666;\n    height: 1px;\n    width: calc(100% - 20px);\n    -moz-width: calc(100% - 20px);\n    -webkit-width: calc(100% - 20px);\n}\n\n.graphmenu-entry .property_name {\n    display: inline-block;\n    text-align: left;\n    min-width: 80px;\n    min-height: 1.2em;\n}\n\n.graphmenu-entry .property_value,\n.litemenu-entry .property_value {\n    display: inline-block;\n    background-color: rgba(0, 0, 0, 0.5);\n    text-align: right;\n    min-width: 80px;\n    min-height: 1.2em;\n    vertical-align: middle;\n    padding-right: 10px;\n}\n\n.graphdialog {\n    position: absolute;\n    top: 10px;\n    left: 10px;\n    min-height: 2em;\n    background-color: #333;\n    font-size: 1.2em;\n    box-shadow: 0 0 10px black !important;\n\tz-index: 10;\n}\n\n.graphdialog.rounded {\n    border-radius: 12px;\n    padding-right: 2px;\n}\n\n.graphdialog .name {\n    display: inline-block;\n    min-width: 60px;\n    min-height: 1.5em;\n    padding-left: 10px;\n}\n\n.graphdialog input,\n.graphdialog textarea,\n.graphdialog select {\n    margin: 3px;\n    min-width: 60px;\n    min-height: 1.5em;\n    background-color: black;\n    border: 0;\n    color: white;\n    padding-left: 10px;\n    outline: none;\n}\n\n.graphdialog textarea {\n\tmin-height: 150px;\n}\n\n.graphdialog button {\n    margin-top: 3px;\n    vertical-align: top;\n    background-color: #999;\n\tborder: 0;\n}\n\n.graphdialog button.rounded,\n.graphdialog input.rounded {\n    border-radius: 0 12px 12px 0;\n}\n\n.graphdialog .helper {\n    overflow: auto;\n    max-height: 200px;\n}\n\n.graphdialog .help-item {\n    padding-left: 10px;\n}\n\n.graphdialog .help-item:hover,\n.graphdialog .help-item.selected {\n    cursor: pointer;\n    background-color: white;\n    color: black;\n}\n\n.litegraph .dialog {\n    min-height: 0;\n}\n.litegraph .dialog .dialog-content {\ndisplay: block;\n}\n.litegraph .dialog .dialog-content .subgraph_property {\npadding: 5px;\n}\n.litegraph .dialog .dialog-footer {\nmargin: 0;\n}\n.litegraph .dialog .dialog-footer .subgraph_property {\nmargin-top: 0;\ndisplay: flex;\nalign-items: center;\npadding: 5px;\n}\n.litegraph .dialog .dialog-footer .subgraph_property .name {\nflex: 1;\n}\n.litegraph .graphdialog {\ndisplay: flex;\nalign-items: center;\nborder-radius: 20px;\npadding: 4px 10px;\nposition: fixed;\n}\n.litegraph .graphdialog .name {\npadding: 0;\nmin-height: 0;\nfont-size: 16px;\nvertical-align: middle;\n}\n.litegraph .graphdialog .value {\nfont-size: 16px;\nmin-height: 0;\nmargin: 0 10px;\npadding: 2px 5px;\n}\n.litegraph .graphdialog input[type=\"checkbox\"] {\nwidth: 16px;\nheight: 16px;\n}\n.litegraph .graphdialog button {\npadding: 4px 18px;\nborder-radius: 20px;\ncursor: pointer;\n}\n  \n"
  },
  {
    "path": "doc/api.js",
    "content": "YUI.add(\"yuidoc-meta\", function(Y) {\n   Y.YUIDoc = { meta: {\n    \"classes\": [\n        \"ContextMenu\",\n        \"LGraph\",\n        \"LGraphCanvas\",\n        \"LGraphNode\",\n        \"LiteGraph\"\n    ],\n    \"modules\": [],\n    \"allModules\": [],\n    \"elements\": []\n} };\n});"
  },
  {
    "path": "doc/assets/css/main.css",
    "content": "/*\nFont sizes for all selectors other than the body are given in percentages,\nwith 100% equal to 13px. To calculate a font size percentage, multiply the\ndesired size in pixels by 7.6923076923.\n\nHere's a quick lookup table:\n\n10px - 76.923%\n11px - 84.615%\n12px - 92.308%\n13px - 100%\n14px - 107.692%\n15px - 115.385%\n16px - 123.077%\n17px - 130.769%\n18px - 138.462%\n19px - 146.154%\n20px - 153.846%\n*/\n\nhtml {\n    background: #fff;\n    color: #333;\n    overflow-y: scroll;\n}\n\nbody {\n    /*font: 13px/1.4 'Lucida Grande', 'Lucida Sans Unicode', 'DejaVu Sans', 'Bitstream Vera Sans', 'Helvetica', 'Arial', sans-serif;*/\n    font: 13px/1.4 'Helvetica', 'Arial', sans-serif;\n    margin: 0;\n    padding: 0;\n}\n\n/* -- Links ----------------------------------------------------------------- */\na {\n    color: #356de4;\n    text-decoration: none;\n}\n\n.hidden {\n    display: none;\n}\n\na:hover { text-decoration: underline; }\n\n/* \"Jump to Table of Contents\" link is shown to assistive tools, but hidden from\n   sight until it's focused. */\n.jump {\n    position: absolute;\n    padding: 3px 6px;\n    left: -99999px;\n    top: 0;\n}\n\n.jump:focus { left: 40%; }\n\n/* -- Paragraphs ------------------------------------------------------------ */\np { margin: 1.3em 0; }\ndd p, td p { margin-bottom: 0; }\ndd p:first-child, td p:first-child { margin-top: 0; }\n\n/* -- Headings -------------------------------------------------------------- */\nh1, h2, h3, h4, h5, h6 {\n    color: #D98527;/*was #f80*/\n    font-family: 'Trebuchet MS', sans-serif;\n    font-weight: bold;\n    line-height: 1.1;\n    margin: 1.1em 0 0.5em;\n}\n\nh1 {\n    font-size: 184.6%;\n    color: #30418C;\n    margin: 0.75em 0 0.5em;\n}\n\nh2 {\n    font-size: 153.846%;\n    color: #E48A2B;\n}\n\nh3 { font-size: 138.462%; }\n\nh4 {\n    border-bottom: 1px solid #DBDFEA;\n    color: #E48A2B;\n    font-size: 115.385%;\n    font-weight: normal;\n    padding-bottom: 2px;\n}\n\nh5, h6 { font-size: 107.692%; }\n\n/* -- Code and examples ----------------------------------------------------- */\ncode, kbd, pre, samp {\n    font-family: Menlo, Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace;\n    font-size: 92.308%;\n    line-height: 1.35;\n}\n\np code, p kbd, p samp, li code {\n    background: #FCFBFA;\n    border: 1px solid #EFEEED;\n    padding: 0 3px;\n}\n\na code, a kbd, a samp,\npre code, pre kbd, pre samp,\ntable code, table kbd, table samp,\n.intro code, .intro kbd, .intro samp,\n.toc code, .toc kbd, .toc samp {\n    background: none;\n    border: none;\n    padding: 0;\n}\n\npre.code, pre.terminal, pre.cmd {\n    overflow-x: auto;\n    *overflow-x: scroll;\n    padding: 0.3em 0.6em;\n}\n\npre.code {\n    background: #FCFBFA;\n    border: 1px solid #EFEEED;\n    border-left-width: 5px;\n}\n\npre.terminal, pre.cmd {\n    background: #F0EFFC;\n    border: 1px solid #D0CBFB;\n    border-left: 5px solid #D0CBFB;\n}\n\n/* Don't reduce the font size of <code>/<kbd>/<samp> elements inside <pre>\n   blocks. */\npre code, pre kbd, pre samp { font-size: 100%; }\n\n/* Used to denote text that shouldn't be selectable, such as line numbers or\n   shell prompts. Guess which browser this doesn't work in. */\n.noselect {\n    -moz-user-select: -moz-none;\n    -khtml-user-select: none;\n    -webkit-user-select: none;\n    -o-user-select: none;\n    user-select: none;\n}\n\n/* -- Lists ----------------------------------------------------------------- */\ndd { margin: 0.2em 0 0.7em 1em; }\ndl { margin: 1em 0; }\ndt { font-weight: bold; }\n\n/* -- Tables ---------------------------------------------------------------- */\ncaption, th { text-align: left; }\n\ntable {\n    border-collapse: collapse;\n    width: 100%;\n}\n\ntd, th {\n    border: 1px solid #fff;\n    padding: 5px 12px;\n    vertical-align: top;\n}\n\ntd { background: #E6E9F5; }\ntd dl { margin: 0; }\ntd dl dl { margin: 1em 0; }\ntd pre:first-child { margin-top: 0; }\n\nth {\n    background: #D2D7E6;/*#97A0BF*/\n    border-bottom: none;\n    border-top: none;\n    color: #000;/*#FFF1D5*/\n    font-family: 'Trebuchet MS', sans-serif;\n    font-weight: bold;\n    line-height: 1.3;\n    white-space: nowrap;\n}\n\n\n/* -- Layout and Content ---------------------------------------------------- */\n#doc {\n    margin: auto;\n    min-width: 1024px;\n}\n\n.content { padding: 0 20px 0 25px; }\n\n.sidebar {\n    padding: 0 15px 0 10px;\n}\n#bd {\n    padding: 7px 0 130px;\n    position: relative;\n    width: 99%;\n}\n\n/* -- Table of Contents ----------------------------------------------------- */\n\n/* The #toc id refers to the single global table of contents, while the .toc\n   class refers to generic TOC lists that could be used throughout the page. */\n\n.toc code, .toc kbd, .toc samp { font-size: 100%; }\n.toc li { font-weight: bold; }\n.toc li li { font-weight: normal; }\n\n/* -- Intro and Example Boxes ----------------------------------------------- */\n/*\n.intro, .example { margin-bottom: 2em; }\n.example {\n    -moz-border-radius: 4px;\n    -webkit-border-radius: 4px;\n    border-radius: 4px;\n    -moz-box-shadow: 0 0 5px #bfbfbf;\n    -webkit-box-shadow: 0 0 5px #bfbfbf;\n    box-shadow: 0 0 5px #bfbfbf;\n    padding: 1em;\n}\n.intro {\n    background: none repeat scroll 0 0 #F0F1F8; border: 1px solid #D4D8EB; padding: 0 1em;\n}\n*/\n\n/* -- Other Styles ---------------------------------------------------------- */\n\n/* These are probably YUI-specific, and should be moved out of Selleck's default\n   theme. */\n\n.button {\n    border: 1px solid #dadada;\n    -moz-border-radius: 3px;\n    -webkit-border-radius: 3px;\n    border-radius: 3px;\n    color: #444;\n    display: inline-block;\n    font-family: Helvetica, Arial, sans-serif;\n    font-size: 92.308%;\n    font-weight: bold;\n    padding: 4px 13px 3px;\n    -moz-text-shadow: 1px 1px 0 #fff;\n    -webkit-text-shadow: 1px 1px 0 #fff;\n    text-shadow: 1px 1px 0 #fff;\n    white-space: nowrap;\n\n    background: #EFEFEF; /* old browsers */\n    background: -moz-linear-gradient(top, #f5f5f5 0%, #efefef 50%, #e5e5e5 51%, #dfdfdf 100%); /* firefox */\n    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f5f5f5), color-stop(50%,#efefef), color-stop(51%,#e5e5e5), color-stop(100%,#dfdfdf)); /* webkit */\n    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f5f5f5', endColorstr='#dfdfdf',GradientType=0 ); /* ie */\n}\n\n.button:hover {\n    border-color: #466899;\n    color: #fff;\n    text-decoration: none;\n    -moz-text-shadow: 1px 1px 0 #222;\n    -webkit-text-shadow: 1px 1px 0 #222;\n    text-shadow: 1px 1px 0 #222;\n\n    background: #6396D8; /* old browsers */\n    background: -moz-linear-gradient(top, #6396D8 0%, #5A83BC 50%, #547AB7 51%, #466899 100%); /* firefox */\n    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#6396D8), color-stop(50%,#5A83BC), color-stop(51%,#547AB7), color-stop(100%,#466899)); /* webkit */\n    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#6396D8', endColorstr='#466899',GradientType=0 ); /* ie */\n}\n\n.newwindow { text-align: center; }\n\n.header .version em {\n    display: block;\n    text-align: right;\n}\n\n\n#classdocs .item {\n    border-bottom: 1px solid #466899;\n    margin: 1em 0;\n    padding: 1.5em;\n}\n\n#classdocs .item .params p,\n    #classdocs .item .returns p,{\n    display: inline;\n}\n\n#classdocs .item em code, #classdocs .item em.comment {\n    color: green;\n}\n\n#classdocs .item em.comment a {\n    color: green;\n    text-decoration: underline;\n}\n\n#classdocs .foundat {\n    font-size: 11px;\n    font-style: normal;\n}\n\n.attrs .emits {\n    margin-left: 2em;\n    padding: .5em;\n    border-left: 1px dashed #ccc;\n}\n\nabbr {\n    border-bottom: 1px dashed #ccc;\n    font-size: 80%;\n    cursor: help;\n}\n\n.prettyprint li.L0, \n.prettyprint li.L1, \n.prettyprint li.L2, \n.prettyprint li.L3, \n.prettyprint li.L5, \n.prettyprint li.L6, \n.prettyprint li.L7, \n.prettyprint li.L8 {\n    list-style: decimal;\n}\n\nul li p {\n    margin-top: 0;\n}\n\n.method .name {\n    font-size: 110%;\n}\n\n.apidocs .methods .extends .method,\n.apidocs .properties .extends .property,\n.apidocs .attrs .extends .attr,\n.apidocs .events .extends .event {\n    font-weight: bold;\n}\n\n.apidocs .methods .extends .inherited,\n.apidocs .properties .extends .inherited,\n.apidocs .attrs .extends .inherited,\n.apidocs .events .extends .inherited {\n    font-weight: normal;\n}\n\n#hd {\n    background: whiteSmoke;\n    background: -moz-linear-gradient(top,#DCDBD9 0,#F6F5F3 100%);\n    background: -webkit-gradient(linear,left top,left bottom,color-stop(0%,#DCDBD9),color-stop(100%,#F6F5F3));\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#dcdbd9',endColorstr='#F6F5F3',GradientType=0);\n    border-bottom: 1px solid #DFDFDF;\n    padding: 0 15px 1px 20px;\n    margin-bottom: 15px;\n}\n\n#hd img {\n    margin-right: 10px;\n    vertical-align: middle;\n}\n\n\n/* -- API Docs CSS ---------------------------------------------------------- */\n\n/*\nThis file is organized so that more generic styles are nearer the top, and more\nspecific styles are nearer the bottom of the file. This allows us to take full\nadvantage of the cascade to avoid redundant style rules. Please respect this\nconvention when making changes.\n*/\n\n/* -- Generic TabView styles ------------------------------------------------ */\n\n/*\nThese styles apply to all API doc tabviews. To change styles only for a\nspecific tabview, see the other sections below.\n*/\n\n.yui3-js-enabled .apidocs .tabview {\n    visibility: hidden; /* Hide until the TabView finishes rendering. */\n    _visibility: visible;\n}\n\n.apidocs .tabview.yui3-tabview-content { visibility: visible; }\n.apidocs .tabview .yui3-tabview-panel { background: #fff; }\n\n/* -- Generic Content Styles ------------------------------------------------ */\n\n/* Headings */\nh2, h3, h4, h5, h6 {\n    border: none;\n    color: #30418C;\n    font-weight: bold;\n    text-decoration: none;\n}\n\n.link-docs {\n    float: right;\n    font-size: 15px;\n    margin: 4px 4px 6px;\n    padding: 6px 30px 5px;\n}\n\n.apidocs { zoom: 1; }\n\n/* Generic box styles. */\n.apidocs .box {\n    border: 1px solid;\n    border-radius: 3px;\n    margin: 1em 0;\n    padding: 0 1em;\n}\n\n/* A flag is a compact, capsule-like indicator of some kind. It's used to\n   indicate private and protected items, item return types, etc. in an\n   attractive and unobtrusive way. */\n.apidocs .flag {\n    background: #bababa;\n    border-radius: 3px;\n    color: #fff;\n    font-size: 11px;\n    margin: 0 0.5em;\n    padding: 2px 4px 1px;\n}\n\n/* Class/module metadata such as \"Uses\", \"Extends\", \"Defined in\", etc. */\n.apidocs .meta {\n    background: #f9f9f9;\n    border-color: #efefef;\n    color: #555;\n    font-size: 11px;\n    padding: 3px 6px;\n}\n\n.apidocs .meta p { margin: 0; }\n\n/* Deprecation warning. */\n.apidocs .box.deprecated,\n.apidocs .flag.deprecated {\n    background: #fdac9f;\n    border: 1px solid #fd7775;\n}\n\n.apidocs .box.deprecated p { margin: 0.5em 0; }\n.apidocs .flag.deprecated { color: #333; }\n\n/* Module/Class intro description. */\n.apidocs .intro {\n    background: #f0f1f8;\n    border-color: #d4d8eb;\n}\n\n/* Loading spinners. */\n#bd.loading .apidocs,\n#api-list.loading .yui3-tabview-panel {\n    background: #fff url(../img/spinner.gif) no-repeat center 70px;\n    min-height: 150px;\n}\n\n#bd.loading .apidocs .content,\n#api-list.loading .yui3-tabview-panel .apis {\n    display: none;\n}\n\n.apidocs .no-visible-items { color: #666; }\n\n/* Generic inline list. */\n.apidocs ul.inline {\n    display: inline;\n    list-style: none;\n    margin: 0;\n    padding: 0;\n}\n\n.apidocs ul.inline li { display: inline; }\n\n/* Comma-separated list. */\n.apidocs ul.commas li:after { content: ','; }\n.apidocs ul.commas li:last-child:after { content: ''; }\n\n/* Keyboard shortcuts. */\nkbd .cmd { font-family: Monaco, Helvetica; }\n\n/* -- Generic Access Level styles ------------------------------------------- */\n.apidocs .item.protected,\n.apidocs .item.private,\n.apidocs .index-item.protected,\n.apidocs .index-item.deprecated,\n.apidocs .index-item.private {\n    display: none;\n}\n\n.show-deprecated .item.deprecated,\n.show-deprecated .index-item.deprecated,\n.show-protected .item.protected,\n.show-protected .index-item.protected,\n.show-private .item.private,\n.show-private .index-item.private {\n    display: block;\n}\n\n.hide-inherited .item.inherited,\n.hide-inherited .index-item.inherited {\n    display: none;\n}\n\n/* -- Generic Item Index styles --------------------------------------------- */\n.apidocs .index { margin: 1.5em 0 3em; }\n\n.apidocs .index h3 {\n    border-bottom: 1px solid #efefef;\n    color: #333;\n    font-size: 13px;\n    margin: 2em 0 0.6em;\n    padding-bottom: 2px;\n}\n\n.apidocs .index .no-visible-items { margin-top: 2em; }\n\n.apidocs .index-list {\n    border-color: #efefef;\n    font-size: 12px;\n    list-style: none;\n    margin: 0;\n    padding: 0;\n    -moz-column-count: 4;\n    -moz-column-gap: 10px;\n    -moz-column-width: 170px;\n    -ms-column-count: 4;\n    -ms-column-gap: 10px;\n    -ms-column-width: 170px;\n    -o-column-count: 4;\n    -o-column-gap: 10px;\n    -o-column-width: 170px;\n    -webkit-column-count: 4;\n    -webkit-column-gap: 10px;\n    -webkit-column-width: 170px;\n    column-count: 4;\n    column-gap: 10px;\n    column-width: 170px;\n}\n\n.apidocs .no-columns .index-list {\n    -moz-column-count: 1;\n    -ms-column-count: 1;\n    -o-column-count: 1;\n    -webkit-column-count: 1;\n    column-count: 1;\n}\n\n.apidocs .index-item { white-space: nowrap; }\n\n.apidocs .index-item .flag {\n    background: none;\n    border: none;\n    color: #afafaf;\n    display: inline;\n    margin: 0 0 0 0.2em;\n    padding: 0;\n}\n\n/* -- Generic API item styles ----------------------------------------------- */\n.apidocs .args {\n    display: inline;\n    margin: 0 0.5em;\n}\n\n.apidocs .flag.chainable { background: #46ca3b; }\n.apidocs .flag.protected { background: #9b86fc; }\n.apidocs .flag.private { background: #fd6b1b; }\n.apidocs .flag.async { background: #356de4; }\n.apidocs .flag.required { background: #e60923; }\n\n.apidocs .item {\n    border-bottom: 1px solid #efefef;\n    margin: 1.5em 0 2em;\n    padding-bottom: 2em;\n}\n\n.apidocs .item h4,\n.apidocs .item h5,\n.apidocs .item h6 {\n    color: #333;\n    font-family: inherit;\n    font-size: 100%;\n}\n\n.apidocs .item .description p,\n.apidocs .item pre.code {\n    margin: 1em 0 0;\n}\n\n.apidocs .item .meta {\n    background: none;\n    border: none;\n    padding: 0;\n}\n\n.apidocs .item .name {\n    display: inline;\n    font-size: 14px;\n}\n\n.apidocs .item .type,\n.apidocs .item .type a,\n.apidocs .returns-inline {\n    color: #555;\n}\n\n.apidocs .item .type,\n.apidocs .returns-inline {\n    font-size: 11px;\n    margin: 0 0 0 0;\n}\n\n.apidocs .item .type a { border-bottom: 1px dotted #afafaf; }\n.apidocs .item .type a:hover { border: none; }\n\n/* -- Item Parameter List --------------------------------------------------- */\n.apidocs .params-list {\n    list-style: square;\n    margin: 1em 0 0 2em;\n    padding: 0;\n}\n\n.apidocs .param { margin-bottom: 1em; }\n\n.apidocs .param .type,\n.apidocs .param .type a {\n    color: #666;\n}\n\n.apidocs .param .type {\n    margin: 0 0 0 0.5em;\n    *margin-left: 0.5em;\n}\n\n.apidocs .param-name { font-weight: bold; }\n\n/* -- Item \"Emits\" block ---------------------------------------------------- */\n.apidocs .item .emits {\n    background: #f9f9f9;\n    border-color: #eaeaea;\n}\n\n/* -- Item \"Returns\" block -------------------------------------------------- */\n.apidocs .item .returns .type,\n.apidocs .item .returns .type a {\n    font-size: 100%;\n    margin: 0;\n}\n\n/* -- Class Constructor block ----------------------------------------------- */\n.apidocs .constructor .item {\n    border: none;\n    padding-bottom: 0;\n}\n\n/* -- File Source View ------------------------------------------------------ */\n.apidocs .file pre.code,\n#doc .apidocs .file pre.prettyprint {\n    background: inherit;\n    border: none;\n    overflow: visible;\n    padding: 0;\n}\n\n.apidocs .L0,\n.apidocs .L1,\n.apidocs .L2,\n.apidocs .L3,\n.apidocs .L4,\n.apidocs .L5,\n.apidocs .L6,\n.apidocs .L7,\n.apidocs .L8,\n.apidocs .L9 {\n    background: inherit;\n}\n\n/* -- Submodule List -------------------------------------------------------- */\n.apidocs .module-submodule-description {\n    font-size: 12px;\n    margin: 0.3em 0 1em;\n}\n\n.apidocs .module-submodule-description p:first-child { margin-top: 0; }\n\n/* -- Sidebar TabView ------------------------------------------------------- */\n#api-tabview { margin-top: 0.6em; }\n\n#api-tabview-filter,\n#api-tabview-panel {\n    border: 1px solid #dfdfdf;\n}\n\n#api-tabview-filter {\n    border-bottom: none;\n    border-top: none;\n    padding: 0.6em 10px 0 10px;\n}\n\n#api-tabview-panel { border-top: none; }\n#api-filter { width: 97%; }\n\n/* -- Content TabView ------------------------------------------------------- */\n#classdocs .yui3-tabview-panel { border: none; }\n\n/* -- Source File Contents -------------------------------------------------- */\n.prettyprint li.L0,\n.prettyprint li.L1,\n.prettyprint li.L2,\n.prettyprint li.L3,\n.prettyprint li.L5,\n.prettyprint li.L6,\n.prettyprint li.L7,\n.prettyprint li.L8 {\n    list-style: decimal;\n}\n\n/* -- API options ----------------------------------------------------------- */\n#api-options {\n    font-size: 11px;\n    margin-top: 2.2em;\n    position: absolute;\n    right: 1.5em;\n}\n\n/*#api-options label { margin-right: 0.6em; }*/\n\n/* -- API list -------------------------------------------------------------- */\n#api-list {\n    margin-top: 1.5em;\n    *zoom: 1;\n}\n\n.apis {\n    font-size: 12px;\n    line-height: 1.4;\n    list-style: none;\n    margin: 0;\n    padding: 0.5em 0 0.5em 0.4em;\n}\n\n.apis a {\n    border: 1px solid transparent;\n    display: block;\n    margin: 0 0 0 -4px;\n    padding: 1px 4px 0;\n    text-decoration: none;\n    _border: none;\n    _display: inline;\n}\n\n.apis a:hover,\n.apis a:focus {\n    background: #E8EDFC;\n    background: -moz-linear-gradient(top, #e8edfc 0%, #becef7 100%);\n    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#E8EDFC), color-stop(100%,#BECEF7));\n    border-color: #AAC0FA;\n    border-radius: 3px;\n    color: #333;\n    outline: none;\n}\n\n.api-list-item a:hover,\n.api-list-item a:focus {\n    font-weight: bold;\n    text-shadow: 1px 1px 1px #fff;\n}\n\n.apis .message { color: #888; }\n.apis .result a { padding: 3px 5px 2px; }\n\n.apis .result .type {\n    right: 4px;\n    top: 7px;\n}\n\n.api-list-item .yui3-highlight {\n    font-weight: bold;\n}\n\n"
  },
  {
    "path": "doc/assets/index.html",
    "content": "<!doctype html>\n<html>\n    <head>\n        <title>Redirector</title>\n        <meta http-equiv=\"refresh\" content=\"0;url=../\">\n    </head>\n    <body>\n        <a href=\"../\">Click here to redirect</a>\n    </body>\n</html>\n"
  },
  {
    "path": "doc/assets/js/api-filter.js",
    "content": "YUI.add('api-filter', function (Y) {\n\nY.APIFilter = Y.Base.create('apiFilter', Y.Base, [Y.AutoCompleteBase], {\n    // -- Initializer ----------------------------------------------------------\n    initializer: function () {\n        this._bindUIACBase();\n        this._syncUIACBase();\n    },\n    getDisplayName: function(name) {\n\n        Y.each(Y.YUIDoc.meta.allModules, function(i) {\n            if (i.name === name && i.displayName) {\n                name = i.displayName;\n            }\n        });\n\n        if (this.get('queryType') === 'elements') {\n            name = '&lt;' + name + '&gt;';\n        }\n\n        return name;\n    }\n\n}, {\n    // -- Attributes -----------------------------------------------------------\n    ATTRS: {\n        resultHighlighter: {\n            value: 'phraseMatch'\n        },\n\n        // May be set to \"classes\", \"elements\" or \"modules\".\n        queryType: {\n            value: 'classes'\n        },\n\n        source: {\n            valueFn: function() {\n                var self = this;\n                return function(q) {\n                    var data = Y.YUIDoc.meta[self.get('queryType')],\n                        out = [];\n                    Y.each(data, function(v) {\n                        if (v.toLowerCase().indexOf(q.toLowerCase()) > -1) {\n                            out.push(v);\n                        }\n                    });\n                    return out;\n                };\n            }\n        }\n    }\n});\n\n}, '3.4.0', {requires: [\n    'autocomplete-base', 'autocomplete-highlighters', 'autocomplete-sources'\n]});\n"
  },
  {
    "path": "doc/assets/js/api-list.js",
    "content": "YUI.add('api-list', function (Y) {\n\nvar Lang   = Y.Lang,\n    YArray = Y.Array,\n\n    APIList = Y.namespace('APIList'),\n\n    classesNode    = Y.one('#api-classes'),\n    elementsNode   = Y.one('#api-elements'),\n    inputNode      = Y.one('#api-filter'),\n    modulesNode    = Y.one('#api-modules'),\n    tabviewNode    = Y.one('#api-tabview'),\n\n    tabs = APIList.tabs = {},\n\n    filter = APIList.filter = new Y.APIFilter({\n        inputNode : inputNode,\n        maxResults: 1000,\n\n        on: {\n            results: onFilterResults\n        }\n    }),\n\n    search = APIList.search = new Y.APISearch({\n        inputNode : inputNode,\n        maxResults: 100,\n\n        on: {\n            clear  : onSearchClear,\n            results: onSearchResults\n        }\n    }),\n\n    tabview = APIList.tabview = new Y.TabView({\n        srcNode  : tabviewNode,\n        panelNode: '#api-tabview-panel',\n        render   : true,\n\n        on: {\n            selectionChange: onTabSelectionChange\n        }\n    }),\n\n    focusManager = APIList.focusManager = tabviewNode.plug(Y.Plugin.NodeFocusManager, {\n        circular   : true,\n        descendants: '#api-filter, .yui3-tab-panel-selected .api-list-item a, .yui3-tab-panel-selected .result a',\n        keys       : {next: 'down:40', previous: 'down:38'}\n    }).focusManager,\n\n    LIST_ITEM_TEMPLATE =\n        '<li class=\"api-list-item {typeSingular}\">' +\n            '<a href=\"{rootPath}{typePlural}/{name}.html\">{displayName}</a>' +\n        '</li>';\n\n// -- Init ---------------------------------------------------------------------\n\n// Duckpunch FocusManager's key event handling to prevent it from handling key\n// events when a modifier is pressed.\nY.before(function (e, activeDescendant) {\n    if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {\n        return new Y.Do.Prevent();\n    }\n}, focusManager, '_focusPrevious', focusManager);\n\nY.before(function (e, activeDescendant) {\n    if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {\n        return new Y.Do.Prevent();\n    }\n}, focusManager, '_focusNext', focusManager);\n\n// Create a mapping of tabs in the tabview so we can refer to them easily later.\ntabview.each(function (tab, index) {\n    var name = tab.get('label').toLowerCase();\n\n    tabs[name] = {\n        index: index,\n        name : name,\n        tab  : tab\n    };\n});\n\n// Switch tabs on Ctrl/Cmd-Left/Right arrows.\ntabviewNode.on('key', onTabSwitchKey, 'down:37,39');\n\n// Focus the filter input when the `/` key is pressed.\nY.one(Y.config.doc).on('key', onSearchKey, 'down:83');\n\n// Keep the Focus Manager up to date.\ninputNode.on('focus', function () {\n    focusManager.set('activeDescendant', inputNode);\n});\n\n// Update all tabview links to resolved URLs.\ntabview.get('panelNode').all('a').each(function (link) {\n    link.setAttribute('href', link.get('href'));\n});\n\n// -- Private Functions --------------------------------------------------------\nfunction getFilterResultNode() {\n    var queryType = filter.get('queryType');\n    return queryType === 'classes' ? classesNode\n            : queryType === 'elements' ? elementsNode : modulesNode;\n}\n\n// -- Event Handlers -----------------------------------------------------------\nfunction onFilterResults(e) {\n    var frag         = Y.one(Y.config.doc.createDocumentFragment()),\n        resultNode   = getFilterResultNode(),\n        typePlural   = filter.get('queryType'),\n        typeSingular = typePlural === 'classes' ? 'class' : typePlural === 'elements' ? 'element' : 'module';\n\n    if (e.results.length) {\n        YArray.each(e.results, function (result) {\n            frag.append(Lang.sub(LIST_ITEM_TEMPLATE, {\n                rootPath    : APIList.rootPath,\n                displayName : filter.getDisplayName(result.highlighted),\n                name        : result.text,\n                typePlural  : typePlural,\n                typeSingular: typeSingular\n            }));\n        });\n    } else {\n        frag.append(\n            '<li class=\"message\">' +\n                'No ' + typePlural + ' found.' +\n            '</li>'\n        );\n    }\n\n    resultNode.empty(true);\n    resultNode.append(frag);\n\n    focusManager.refresh();\n}\n\nfunction onSearchClear(e) {\n\n    focusManager.refresh();\n}\n\nfunction onSearchKey(e) {\n    var target = e.target;\n\n    if (target.test('input,select,textarea')\n            || target.get('isContentEditable')) {\n        return;\n    }\n\n    e.preventDefault();\n\n    inputNode.focus();\n    focusManager.refresh();\n}\n\nfunction onSearchResults(e) {\n    var frag = Y.one(Y.config.doc.createDocumentFragment());\n\n    if (e.results.length) {\n        YArray.each(e.results, function (result) {\n            frag.append(result.display);\n        });\n    } else {\n        frag.append(\n            '<li class=\"message\">' +\n                'No results found. Maybe you\\'ll have better luck with a ' +\n                'different query?' +\n            '</li>'\n        );\n    }\n\n\n    focusManager.refresh();\n}\n\nfunction onTabSelectionChange(e) {\n    var tab  = e.newVal,\n        name = tab.get('label').toLowerCase();\n\n    tabs.selected = {\n        index: tab.get('index'),\n        name : name,\n        tab  : tab\n    };\n\n    switch (name) {\n    case 'elements':// fallthru\n    case 'classes': // fallthru\n    case 'modules':\n        filter.setAttrs({\n            minQueryLength: 0,\n            queryType     : name\n        });\n\n        search.set('minQueryLength', -1);\n\n        // Only send a request if this isn't the initially-selected tab.\n        if (e.prevVal) {\n            filter.sendRequest(filter.get('value'));\n        }\n        break;\n\n    case 'everything':\n        filter.set('minQueryLength', -1);\n        search.set('minQueryLength', 1);\n\n        if (search.get('value')) {\n            search.sendRequest(search.get('value'));\n        } else {\n            inputNode.focus();\n        }\n        break;\n\n    default:\n        // WTF? We shouldn't be here!\n        filter.set('minQueryLength', -1);\n        search.set('minQueryLength', -1);\n    }\n\n    if (focusManager) {\n        setTimeout(function () {\n            focusManager.refresh();\n        }, 1);\n    }\n}\n\nfunction onTabSwitchKey(e) {\n    var currentTabIndex = tabs.selected.index;\n\n    if (!(e.ctrlKey || e.metaKey)) {\n        return;\n    }\n\n    e.preventDefault();\n\n    switch (e.keyCode) {\n    case 37: // left arrow\n        if (currentTabIndex > 0) {\n            tabview.selectChild(currentTabIndex - 1);\n            inputNode.focus();\n        }\n        break;\n\n    case 39: // right arrow\n        if (currentTabIndex < (Y.Object.size(tabs) - 2)) {\n            tabview.selectChild(currentTabIndex + 1);\n            inputNode.focus();\n        }\n        break;\n    }\n}\n\n}, '3.4.0', {requires: [\n    'api-filter', 'api-search', 'event-key', 'node-focusmanager', 'tabview'\n]});\n"
  },
  {
    "path": "doc/assets/js/api-search.js",
    "content": "YUI.add('api-search', function (Y) {\n\nvar Lang   = Y.Lang,\n    Node   = Y.Node,\n    YArray = Y.Array;\n\nY.APISearch = Y.Base.create('apiSearch', Y.Base, [Y.AutoCompleteBase], {\n    // -- Public Properties ----------------------------------------------------\n    RESULT_TEMPLATE:\n        '<li class=\"result {resultType}\">' +\n            '<a href=\"{url}\">' +\n                '<h3 class=\"title\">{name}</h3>' +\n                '<span class=\"type\">{resultType}</span>' +\n                '<div class=\"description\">{description}</div>' +\n                '<span class=\"className\">{class}</span>' +\n            '</a>' +\n        '</li>',\n\n    // -- Initializer ----------------------------------------------------------\n    initializer: function () {\n        this._bindUIACBase();\n        this._syncUIACBase();\n    },\n\n    // -- Protected Methods ----------------------------------------------------\n    _apiResultFilter: function (query, results) {\n        // Filter components out of the results.\n        return YArray.filter(results, function (result) {\n            return result.raw.resultType === 'component' ? false : result;\n        });\n    },\n\n    _apiResultFormatter: function (query, results) {\n        return YArray.map(results, function (result) {\n            var raw  = Y.merge(result.raw), // create a copy\n                desc = raw.description || '';\n\n            // Convert description to text and truncate it if necessary.\n            desc = Node.create('<div>' + desc + '</div>').get('text');\n\n            if (desc.length > 65) {\n                desc = Y.Escape.html(desc.substr(0, 65)) + ' &hellip;';\n            } else {\n                desc = Y.Escape.html(desc);\n            }\n\n            raw['class'] || (raw['class'] = '');\n            raw.description = desc;\n\n            // Use the highlighted result name.\n            raw.name = result.highlighted;\n\n            return Lang.sub(this.RESULT_TEMPLATE, raw);\n        }, this);\n    },\n\n    _apiTextLocator: function (result) {\n        return result.displayName || result.name;\n    }\n}, {\n    // -- Attributes -----------------------------------------------------------\n    ATTRS: {\n        resultFormatter: {\n            valueFn: function () {\n                return this._apiResultFormatter;\n            }\n        },\n\n        resultFilters: {\n            valueFn: function () {\n                return this._apiResultFilter;\n            }\n        },\n\n        resultHighlighter: {\n            value: 'phraseMatch'\n        },\n\n        resultListLocator: {\n            value: 'data.results'\n        },\n\n        resultTextLocator: {\n            valueFn: function () {\n                return this._apiTextLocator;\n            }\n        },\n\n        source: {\n            value: '/api/v1/search?q={query}&count={maxResults}'\n        }\n    }\n});\n\n}, '3.4.0', {requires: [\n    'autocomplete-base', 'autocomplete-highlighters', 'autocomplete-sources',\n    'escape'\n]});\n"
  },
  {
    "path": "doc/assets/js/apidocs.js",
    "content": "YUI().use(\n    'yuidoc-meta',\n    'api-list', 'history-hash', 'node-screen', 'node-style', 'pjax',\nfunction (Y) {\n\nvar win          = Y.config.win,\n    localStorage = win.localStorage,\n\n    bdNode = Y.one('#bd'),\n\n    pjax,\n    defaultRoute,\n\n    classTabView,\n    selectedTab;\n\n// Kill pjax functionality unless serving over HTTP.\nif (!Y.getLocation().protocol.match(/^https?\\:/)) {\n    Y.Router.html5 = false;\n}\n\n// Create the default route with middleware which enables syntax highlighting\n// on the loaded content.\ndefaultRoute = Y.Pjax.defaultRoute.concat(function (req, res, next) {\n    prettyPrint();\n    bdNode.removeClass('loading');\n\n    next();\n});\n\npjax = new Y.Pjax({\n    container      : '#docs-main',\n    contentSelector: '#docs-main > .content',\n    linkSelector   : '#bd a',\n    titleSelector  : '#xhr-title',\n\n    navigateOnHash: true,\n    root          : '/',\n    routes        : [\n        // -- / ----------------------------------------------------------------\n        {\n            path     : '/(index.html)?',\n            callbacks: defaultRoute\n        },\n\n        // -- /elements/* -------------------------------------------------------\n        {\n            path     : '/elements/:element.html*',\n            callbacks: defaultRoute\n        },\n\n        // -- /classes/* -------------------------------------------------------\n        {\n            path     : '/classes/:class.html*',\n            callbacks: [defaultRoute, 'handleClasses']\n        },\n\n        // -- /files/* ---------------------------------------------------------\n        {\n            path     : '/files/*file',\n            callbacks: [defaultRoute, 'handleFiles']\n        },\n\n        // -- /modules/* -------------------------------------------------------\n        {\n            path     : '/modules/:module.html*',\n            callbacks: defaultRoute\n        }\n    ]\n});\n\n// -- Utility Functions --------------------------------------------------------\n\npjax.checkVisibility = function (tab) {\n    tab || (tab = selectedTab);\n\n    if (!tab) { return; }\n\n    var panelNode = tab.get('panelNode'),\n        visibleItems;\n\n    // If no items are visible in the tab panel due to the current visibility\n    // settings, display a message to that effect.\n    visibleItems = panelNode.all('.item,.index-item').some(function (itemNode) {\n        if (itemNode.getComputedStyle('display') !== 'none') {\n            return true;\n        }\n    });\n\n    panelNode.all('.no-visible-items').remove();\n\n    if (!visibleItems) {\n        if (Y.one('#index .index-item')) {\n            panelNode.append(\n                '<div class=\"no-visible-items\">' +\n                    '<p>' +\n                    'Some items are not shown due to the current visibility ' +\n                    'settings. Use the checkboxes at the upper right of this ' +\n                    'page to change the visibility settings.' +\n                    '</p>' +\n                '</div>'\n            );\n        } else {\n            panelNode.append(\n                '<div class=\"no-visible-items\">' +\n                    '<p>' +\n                    'This class doesn\\'t provide any methods, properties, ' +\n                    'attributes, or events.' +\n                    '</p>' +\n                '</div>'\n            );\n        }\n    }\n\n    // Hide index sections without any visible items.\n    Y.all('.index-section').each(function (section) {\n        var items        = 0,\n            visibleItems = 0;\n\n        section.all('.index-item').each(function (itemNode) {\n            items += 1;\n\n            if (itemNode.getComputedStyle('display') !== 'none') {\n                visibleItems += 1;\n            }\n        });\n\n        section.toggleClass('hidden', !visibleItems);\n        section.toggleClass('no-columns', visibleItems < 4);\n    });\n};\n\npjax.initClassTabView = function () {\n    if (!Y.all('#classdocs .api-class-tab').size()) {\n        return;\n    }\n\n    if (classTabView) {\n        classTabView.destroy();\n        selectedTab = null;\n    }\n\n    classTabView = new Y.TabView({\n        srcNode: '#classdocs',\n\n        on: {\n            selectionChange: pjax.onTabSelectionChange\n        }\n    });\n\n    pjax.updateTabState();\n    classTabView.render();\n};\n\npjax.initLineNumbers = function () {\n    var hash      = win.location.hash.substring(1),\n        container = pjax.get('container'),\n        hasLines, node;\n\n    // Add ids for each line number in the file source view.\n    container.all('.linenums>li').each(function (lineNode, index) {\n        lineNode.set('id', 'l' + (index + 1));\n        lineNode.addClass('file-line');\n        hasLines = true;\n    });\n\n    // Scroll to the desired line.\n    if (hasLines && /^l\\d+$/.test(hash)) {\n        if ((node = container.getById(hash))) {\n            win.scroll(0, node.getY());\n        }\n    }\n};\n\npjax.initRoot = function () {\n    var terminators = /^(?:classes|files|elements|modules)$/,\n        parts       = pjax._getPathRoot().split('/'),\n        root        = [],\n        i, len, part;\n\n    for (i = 0, len = parts.length; i < len; i += 1) {\n        part = parts[i];\n\n        if (part.match(terminators)) {\n            // Makes sure the path will end with a \"/\".\n            root.push('');\n            break;\n        }\n\n        root.push(part);\n    }\n\n    pjax.set('root', root.join('/'));\n};\n\npjax.updateTabState = function (src) {\n    var hash = win.location.hash.substring(1),\n        defaultTab, node, tab, tabPanel;\n\n    function scrollToNode() {\n        if (node.hasClass('protected')) {\n            Y.one('#api-show-protected').set('checked', true);\n            pjax.updateVisibility();\n        }\n\n        if (node.hasClass('private')) {\n            Y.one('#api-show-private').set('checked', true);\n            pjax.updateVisibility();\n        }\n\n        setTimeout(function () {\n            // For some reason, unless we re-get the node instance here,\n            // getY() always returns 0.\n            var node = Y.one('#classdocs').getById(hash);\n            win.scrollTo(0, node.getY() - 70);\n        }, 1);\n    }\n\n    if (!classTabView) {\n        return;\n    }\n\n    if (src === 'hashchange' && !hash) {\n        defaultTab = 'index';\n    } else {\n        if (localStorage) {\n            defaultTab = localStorage.getItem('tab_' + pjax.getPath()) ||\n                'index';\n        } else {\n            defaultTab = 'index';\n        }\n    }\n\n    if (hash && (node = Y.one('#classdocs').getById(hash))) {\n        if ((tabPanel = node.ancestor('.api-class-tabpanel', true))) {\n            if ((tab = Y.one('#classdocs .api-class-tab.' + tabPanel.get('id')))) {\n                if (classTabView.get('rendered')) {\n                    Y.Widget.getByNode(tab).set('selected', 1);\n                } else {\n                    tab.addClass('yui3-tab-selected');\n                }\n            }\n        }\n\n        // Scroll to the desired element if this is a hash URL.\n        if (node) {\n            if (classTabView.get('rendered')) {\n                scrollToNode();\n            } else {\n                classTabView.once('renderedChange', scrollToNode);\n            }\n        }\n    } else {\n        tab = Y.one('#classdocs .api-class-tab.' + defaultTab);\n\n        // When the `defaultTab` node isn't found, `localStorage` is stale.\n        if (!tab && defaultTab !== 'index') {\n            tab = Y.one('#classdocs .api-class-tab.index');\n        }\n\n        if (classTabView.get('rendered')) {\n            Y.Widget.getByNode(tab).set('selected', 1);\n        } else {\n            tab.addClass('yui3-tab-selected');\n        }\n    }\n};\n\npjax.updateVisibility = function () {\n    var container = pjax.get('container');\n\n    container.toggleClass('hide-inherited',\n            !Y.one('#api-show-inherited').get('checked'));\n\n    container.toggleClass('show-deprecated',\n            Y.one('#api-show-deprecated').get('checked'));\n\n    container.toggleClass('show-protected',\n            Y.one('#api-show-protected').get('checked'));\n\n    container.toggleClass('show-private',\n            Y.one('#api-show-private').get('checked'));\n\n    pjax.checkVisibility();\n};\n\n// -- Route Handlers -----------------------------------------------------------\n\npjax.handleClasses = function (req, res, next) {\n    var status = res.ioResponse.status;\n\n    // Handles success and local filesystem XHRs.\n    if (res.ioResponse.readyState === 4 && (!status || (status >= 200 && status < 300))) {\n        pjax.initClassTabView();\n    }\n\n    next();\n};\n\npjax.handleFiles = function (req, res, next) {\n    var status = res.ioResponse.status;\n\n    // Handles success and local filesystem XHRs.\n    if (res.ioResponse.readyState === 4 && (!status || (status >= 200 && status < 300))) {\n        pjax.initLineNumbers();\n    }\n\n    next();\n};\n\n// -- Event Handlers -----------------------------------------------------------\n\npjax.onNavigate = function (e) {\n    var hash         = e.hash,\n        originTarget = e.originEvent && e.originEvent.target,\n        tab;\n\n    if (hash) {\n        tab = originTarget && originTarget.ancestor('.yui3-tab', true);\n\n        if (hash === win.location.hash) {\n            pjax.updateTabState('hashchange');\n        } else if (!tab) {\n            win.location.hash = hash;\n        }\n\n        e.preventDefault();\n        return;\n    }\n\n    // Only scroll to the top of the page when the URL doesn't have a hash.\n    this.set('scrollToTop', !e.url.match(/#.+$/));\n\n    bdNode.addClass('loading');\n};\n\npjax.onOptionClick = function (e) {\n    pjax.updateVisibility();\n};\n\npjax.onTabSelectionChange = function (e) {\n    var tab   = e.newVal,\n        tabId = tab.get('contentBox').getAttribute('href').substring(1);\n\n    selectedTab = tab;\n\n    // If switching from a previous tab (i.e., this is not the default tab),\n    // replace the history entry with a hash URL that will cause this tab to\n    // be selected if the user navigates away and then returns using the back\n    // or forward buttons.\n    if (e.prevVal && localStorage) {\n        localStorage.setItem('tab_' + pjax.getPath(), tabId);\n    }\n\n    pjax.checkVisibility(tab);\n};\n\n// -- Init ---------------------------------------------------------------------\n\npjax.on('navigate', pjax.onNavigate);\n\npjax.initRoot();\npjax.upgrade();\npjax.initClassTabView();\npjax.initLineNumbers();\npjax.updateVisibility();\n\nY.APIList.rootPath = pjax.get('root');\n\nY.one('#api-options').delegate('click', pjax.onOptionClick, 'input');\n\nY.on('hashchange', function (e) {\n    pjax.updateTabState('hashchange');\n}, win);\n\n});\n"
  },
  {
    "path": "doc/assets/js/yui-prettify.js",
    "content": "YUI().use('node', function(Y) {\n    var code = Y.all('.prettyprint.linenums');\n    if (code.size()) {\n        code.each(function(c) {\n            var lis = c.all('ol li'),\n                l = 1;\n            lis.each(function(n) {\n                n.prepend('<a name=\"LINENUM_' + l + '\"></a>');\n                l++;\n            });\n        });\n        var h = location.hash;\n        location.hash = '';\n        h = h.replace('LINE_', 'LINENUM_');\n        location.hash = h;\n    }\n});\n"
  },
  {
    "path": "doc/assets/vendor/prettify/CHANGES.html",
    "content": "<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <title>Change Log</title>\n  </head>\n  <body bgcolor=\"white\">\n    <a style=\"float:right\" href=\"README.html\">README</a>\n\n    <h1>Known Issues</h1>\n    <ul>\n      <li>Perl formatting is really crappy.  Partly because the author is lazy and\n      partly because Perl is\n      <a href=\"http://www.perlmonks.org/?node_id=663393\">hard</a> to parse.\n      <li>On some browsers, <code>&lt;code&gt;</code> elements with newlines in the text\n      which use CSS to specify <code>white-space:pre</code> will have the newlines\n      improperly stripped if the element is not attached to the document at the time\n      the stripping is done.  Also, on IE 6, all newlines will be stripped from\n      <code>&lt;code&gt;</code> elements because of the way IE6 produces\n      <code>innerHTML</code>.  Workaround: use <code>&lt;pre&gt;</code> for code with\n      newlines.\n    </ul>\n\n    <h1>Change Log</h1>\n    <h2>29 March 2007</h2>\n    <ul>\n      <li>Added <a href=\"tests/prettify_test.html#PHP\">tests</a> for PHP support\n        to address \n      <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=3\"\n       >issue 3</a>.\n      <li>Fixed\n      <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=6\"\n       >bug</a>: <code>prettyPrintOne</code> was not halting.  This was not\n        reachable through the normal entry point.\n      <li>Fixed\n      <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=4\"\n       >bug</a>: recursing into a script block or PHP tag that was not properly\n        closed would not silently drop the content.\n        (<a href=\"tests/prettify_test.html#issue4\">test</a>)\n      <li>Fixed\n      <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=8\"\n       >bug</a>: was eating tabs\n        (<a href=\"tests/prettify_test.html#issue8\">test</a>)\n      <li>Fixed entity handling so that the caveat\n        <blockquote>\n          <p>Caveats: please properly escape less-thans.  <tt>x&amp;lt;y</tt>\n          instead of <tt>x&lt;y</tt>, and use <tt>&quot;</tt> instead of \n          <tt>&amp;quot;</tt> for string delimiters.</p>\n        </blockquote>\n        is no longer applicable.\n      <li>Added noisefree's C#\n      <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=4\"\n       >patch</a>\n      <li>Added a <a href=\"http://google-code-prettify.googlecode.com/files/prettify-small.zip\">distribution</a> that has comments and\n        whitespace removed to reduce download size from 45.5kB to 12.8kB.\n    </ul>\n    <h2>4 Jul 2008</h2>\n    <ul>\n      <li>Added <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=17\">language specific formatters</a> that are triggered by the presence\n      of a <code>lang-&lt;language-file-extension&gt;</code></li>\n      <li>Fixed <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=29\">bug</a>: python handling of <code>'''string'''</code>\n      <li>Fixed bug: <code>/</code> in regex <code>[charsets] should not end regex</code>\n    </ul>\n    <h2>5 Jul 2008</h2>\n    <ul>\n      <li>Defined language extensions for Lisp and Lua</code>\n    </ul>\n    <h2>14 Jul 2008</h2>\n    <ul>\n      <li>Language handlers for F#, OCAML, SQL</code>\n      <li>Support for <code>nocode</code> spans to allow embedding of line\n      numbers and code annotations which should not be styled or otherwise\n      affect the tokenization of prettified code.\n      See the issue 22\n      <a href=\"tests/prettify_test.html#issue22\">testcase</a>.</code>\n    </ul>\n    <h2>6 Jan 2009</h2>\n    <ul>\n      <li>Language handlers for Visual Basic, Haskell, CSS, and WikiText</li>\n      <li>Added <tt>.mxml</tt> extension to the markup style handler for\n        Flex <a href=\"http://en.wikipedia.org/wiki/MXML\">MXML files</a>.  See\n        <a\n        href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=37\"\n        >issue 37</a>.\n      <li>Added <tt>.m</tt> extension to the C style handler so that Objective\n        C source files properly highlight.  See\n        <a\n        href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=58\"\n       >issue 58</a>.\n      <li>Changed HTML lexer to use the same embedded source mechanism as the\n        wiki language handler, and changed to use the registered\n        CSS handler for STYLE element content.\n    </ul>\n    <h2>21 May 2009</h2>\n    <ul>\n      <li>Rewrote to improve performance on large files.\n        See <a href=\"http://mikesamuel.blogspot.com/2009/05/efficient-parsing-in-javascript.html\">benchmarks</a>.</li>\n      <li>Fixed bugs with highlighting of Haskell line comments, Lisp\n        number literals, Lua strings, C preprocessor directives,\n        newlines in Wiki code on Windows, and newlines in IE6.</li>\n    </ul>\n    <h2>14 August 2009</h2>\n    <ul>\n      <li>Fixed prettifying of <code>&lt;code&gt;</code> blocks with embedded newlines.\n    </ul>\n    <h2>3 October 2009</h2>\n    <ul>\n      <li>Fixed prettifying of XML/HTML tags that contain uppercase letters.\n    </ul>\n    <h2>19 July 2010</h2>\n    <ul>\n      <li>Added support for line numbers.  Bug\n        <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=22\"\n         >22</a></li>\n      <li>Added YAML support.  Bug\n        <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=123\"\n         >123</a></li>\n      <li>Added VHDL support courtesy Le Poussin.</li>\n      <li>IE performance improvements.  Bug\n        <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=102\"\n         >102</a> courtesy jacobly.</li>\n      <li>A variety of markup formatting fixes courtesy smain and thezbyg.</li>\n      <li>Fixed copy and paste in IE[678].\n      <li>Changed output to use <code>&amp;#160;</code> instead of\n        <code>&amp;nbsp;</code> so that the output works when embedded in XML.\n        Bug\n        <a href=\"http://code.google.com/p/google-code-prettify/issues/detail?id=108\"\n         >108</a>.</li>\n    </ul>\n  </body>\n</html>\n"
  },
  {
    "path": "doc/assets/vendor/prettify/COPYING",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "doc/assets/vendor/prettify/README.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html>\n  <head>\n    <title>Javascript code prettifier</title>\n\n    <link href=\"src/prettify.css\" type=\"text/css\" rel=\"stylesheet\" />\n\n    <script src=\"src/prettify.js\" type=\"text/javascript\"></script>\n\n    <style type=\"text/css\">\n      body { margin-left: .5in }\n      h1, h2, h3, h4, .footer { margin-left: -.4in; }\n    </style>\n  </head>\n\n  <body onload=\"prettyPrint()\" bgcolor=\"white\">\n    <small style=\"float: right\">Languages : <a href=\"README-zh-Hans.html\">CH</a></small>\n    <h1>Javascript code prettifier</h1>\n\n    <h2>Setup</h2>\n    <ol>\n      <li><a href=\"http://code.google.com/p/google-code-prettify/downloads/list\">Download</a> a distribution\n      <li>Include the script and stylesheets in your document\n        (you will need to make sure the css and js file are on your server, and\n         adjust the paths in the <tt>script</tt> and <tt>link</tt> tag)\n        <pre class=\"prettyprint\">\n&lt;link href=\"prettify.css\" type=\"text/css\" rel=\"stylesheet\" />\n&lt;script type=\"text/javascript\" src=\"prettify.js\">&lt;/script></pre>\n      <li>Add <code class=\"prettyprint lang-html\">onload=\"prettyPrint()\"</code> to your\n      document's body tag.\n      <li>Modify the stylesheet to get the coloring you prefer</li>\n    </ol>\n\n    <h2>Usage</h2>\n    <p>Put code snippets in\n    <tt>&lt;pre class=\"prettyprint\"&gt;...&lt;/pre&gt;</tt>\n    or <tt>&lt;code class=\"prettyprint\"&gt;...&lt;/code&gt;</tt>\n    and it will automatically be pretty printed.\n\n    <table summary=\"code examples\">\n      <tr>\n        <th>The original\n        <th>Prettier\n      <tr>\n        <td><pre style=\"border: 1px solid #888;padding: 2px\"\n             ><a name=\"voila1\"></a>class Voila {\npublic:\n  // Voila\n  static const string VOILA = \"Voila\";\n\n  // will not interfere with embedded <a href=\"#voila1\">tags</a>.\n}</pre>\n\n        <td><pre class=\"prettyprint\"><a name=\"voila2\"></a>class Voila {\npublic:\n  // Voila\n  static const string VOILA = \"Voila\";\n\n  // will not interfere with embedded <a href=\"#voila2\">tags</a>.\n}</pre>\n    </table>\n\n    <h2>FAQ</h2>\n    <h3 id=\"langs\">Which languages does it work for?</h3>\n    <p>The comments in <tt>prettify.js</tt> are authoritative but the lexer\n    should work on a number of languages including C and friends,\n    Java, Python, Bash, SQL, HTML, XML, CSS, Javascript, and Makefiles.\n    It works passably on Ruby, PHP, VB, and Awk and a decent subset of Perl\n    and Ruby, but, because of commenting conventions, doesn't work on\n    Smalltalk, or CAML-like languages.</p>\n\n    <p>LISPy languages are supported via an extension:\n    <a href=\"http://code.google.com/p/google-code-prettify/source/browse/trunk/src/lang-lisp.js\"\n     ><code>lang-lisp.js</code></a>.</p>\n    <p>And similarly for\n    <a href=\"http://code.google.com/p/google-code-prettify/source/browse/trunk/src/lang-css.js\"\n     ><code>CSS</code></a>,\n    <a href=\"http://code.google.com/p/google-code-prettify/source/browse/trunk/src/lang-hs.js\"\n     ><code>Haskell</code></a>,\n    <a href=\"http://code.google.com/p/google-code-prettify/source/browse/trunk/src/lang-lua.js\"\n     ><code>Lua</code></a>,\n    <a href=\"http://code.google.com/p/google-code-prettify/source/browse/trunk/src/lang-ml.js\"\n     ><code>OCAML, SML, F#</code></a>,\n    <a href=\"http://code.google.com/p/google-code-prettify/source/browse/trunk/src/lang-vb.js\"\n     ><code>Visual Basic</code></a>,\n    <a href=\"http://code.google.com/p/google-code-prettify/source/browse/trunk/src/lang-sql.js\"\n     ><code>SQL</code></a>,\n    <a href=\"http://code.google.com/p/google-code-prettify/source/browse/trunk/src/lang-proto.js\"\n     ><code>Protocol Buffers</code></a>, and\n    <a href=\"http://code.google.com/p/google-code-prettify/source/browse/trunk/src/lang-wiki.js\"\n     ><code>WikiText</code></a>..\n\n    <p>If you'd like to add an extension for your favorite language, please\n    look at <tt>src/lang-lisp.js</tt> and file an\n    <a href=\"http://code.google.com/p/google-code-prettify/issues/list\"\n     >issue</a> including your language extension, and a testcase.</p>\n\n    <h3>How do I specify which language my code is in?</h3>\n    <p>You don't need to specify the language since <code>prettyprint()</code>\n    will guess.  You can specify a language by specifying the language extension\n    along with the <code>prettyprint</code> class like so:</p>\n    <pre class=\"prettyprint lang-html\"\n>&lt;pre class=&quot;prettyprint <b>lang-html</b>&quot;&gt;\n  The lang-* class specifies the language file extensions.\n  File extensions supported by default include\n    \"bsh\", \"c\", \"cc\", \"cpp\", \"cs\", \"csh\", \"cyc\", \"cv\", \"htm\", \"html\",\n    \"java\", \"js\", \"m\", \"mxml\", \"perl\", \"pl\", \"pm\", \"py\", \"rb\", \"sh\",\n    \"xhtml\", \"xml\", \"xsl\".\n&lt;/pre&gt;</pre>\n\n    <h3>It doesn't work on <tt>&lt;obfuscated code sample&gt;</tt>?</h3>\n    <p>Yes.  Prettifying obfuscated code is like putting lipstick on a pig\n    &mdash; i.e. outside the scope of this tool.</p>\n\n    <h3>Which browsers does it work with?</h3>\n    <p>It's been tested with IE 6, Firefox 1.5 &amp; 2, and Safari 2.0.4.\n    Look at <a href=\"tests/prettify_test.html\">the test page</a> to see if it\n    works in your browser.</p>\n\n    <h3>What's changed?</h3>\n    <p>See the <a href=\"CHANGES.html\">change log</a></p>\n\n    <h3>Why doesn't Prettyprinting of strings work on WordPress?</h3>\n    <p>Apparently wordpress does \"smart quoting\" which changes close quotes.\n    This causes end quotes to not match up with open quotes.\n    <p>This breaks prettifying as well as copying and pasting of code samples.\n    See\n    <a href=\"http://wordpress.org/support/topic/125038\"\n    >WordPress's help center</a> for info on how to stop smart quoting of code\n    snippets.</p>\n\n    <h3 id=\"linenums\">How do I put line numbers in my code?</h3>\n    <p>You can use the <code>linenums</code> class to turn on line\n    numbering.  If your code doesn't start at line number 1, you can\n    add a colon and a line number to the end of that class as in\n    <code>linenums:52</code>.\n\n    <p>For example\n<pre class=\"prettyprint\">&lt;pre class=\"prettyprint linenums:<b>4</b>\"\n&gt;// This is line 4.\nfoo();\nbar();\nbaz();\nboo();\nfar();\nfaz();\n&lt;pre&gt;</pre>\n    produces\n<pre class=\"prettyprint linenums:4\"\n>// This is line 4.\nfoo();\nbar();\nbaz();\nboo();\nfar();\nfaz();\n</pre>\n\n    <h3>How do I prevent a portion of markup from being marked as code?</h3>\n    <p>You can use the <code>nocode</code> class to identify a span of markup\n    that is not code.\n<pre class=\"prettyprint\">&lt;pre class=prettyprint&gt;\nint x = foo();  /* This is a comment  &lt;span class=\"nocode\"&gt;This is not code&lt;/span&gt;\n  Continuation of comment */\nint y = bar();\n&lt;/pre&gt;</pre>\nproduces\n<pre class=\"prettyprint\">\nint x = foo();  /* This is a comment  <span class=\"nocode\">This is not code</span>\n  Continuation of comment */\nint y = bar();\n</pre>\n\n    <p>For a more complete example see the issue22\n    <a href=\"tests/prettify_test.html#issue22\">testcase</a>.</p>\n\n    <h3>I get an error message \"a is not a function\" or \"opt_whenDone is not a function\"</h3>\n    <p>If you are calling <code>prettyPrint</code> via an event handler, wrap it in a function.\n    Instead of doing\n    <blockquote>\n      <code class=\"prettyprint lang-js\"\n       >addEventListener('load', prettyPrint, false);</code>\n    </blockquote>\n    wrap it in a closure like\n    <blockquote>\n      <code class=\"prettyprint lang-js\"\n       >addEventListener('load', function (event) { prettyPrint() }, false);</code>\n    </blockquote>\n    so that the browser does not pass an event object to <code>prettyPrint</code> which\n    will confuse it.\n\n    <br><br><br>\n\n    <div class=\"footer\">\n<!-- Created: Tue Oct  3 17:51:56 PDT 2006 -->\n<!-- hhmts start -->\nLast modified: Wed Jul 19 13:56:00 PST 2010\n<!-- hhmts end -->\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "doc/assets/vendor/prettify/prettify-min.css",
    "content": ".pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}"
  },
  {
    "path": "doc/assets/vendor/prettify/prettify-min.js",
    "content": "window.PR_SHOULD_USE_CONTINUATION=true;var prettyPrintOne;var prettyPrint;(function(){var O=window;var j=[\"break,continue,do,else,for,if,return,while\"];var v=[j,\"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile\"];var q=[v,\"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof\"];var m=[q,\"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where\"];var y=[q,\"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient\"];var T=[y,\"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where\"];var s=\"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes\";var x=[q,\"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN\"];var t=\"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END\";var J=[j,\"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None\"];var g=[j,\"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END\"];var I=[j,\"case,done,elif,esac,eval,fi,function,in,local,set,then,until\"];var B=[m,T,x,t+J,g,I];var f=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\\d*)\\b/;var D=\"str\";var A=\"kwd\";var k=\"com\";var Q=\"typ\";var H=\"lit\";var M=\"pun\";var G=\"pln\";var n=\"tag\";var F=\"dec\";var K=\"src\";var R=\"atn\";var o=\"atv\";var P=\"nocode\";var N=\"(?:^^\\\\.?|[+-]|[!=]=?=?|\\\\#|%=?|&&?=?|\\\\(|\\\\*=?|[+\\\\-]=|->|\\\\/=?|::?|<<?=?|>>?>?=?|,|;|\\\\?|@|\\\\[|~|{|\\\\^\\\\^?=?|\\\\|\\\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\\\s*\";function l(ab){var af=0;var U=false;var ae=false;for(var X=0,W=ab.length;X<W;++X){var ag=ab[X];if(ag.ignoreCase){ae=true}else{if(/[a-z]/i.test(ag.source.replace(/\\\\u[0-9a-f]{4}|\\\\x[0-9a-f]{2}|\\\\[^ux]/gi,\"\"))){U=true;ae=false;break}}}var aa={b:8,t:9,n:10,v:11,f:12,r:13};function ad(aj){var ai=aj.charCodeAt(0);if(ai!==92){return ai}var ah=aj.charAt(1);ai=aa[ah];if(ai){return ai}else{if(\"0\"<=ah&&ah<=\"7\"){return parseInt(aj.substring(1),8)}else{if(ah===\"u\"||ah===\"x\"){return parseInt(aj.substring(2),16)}else{return aj.charCodeAt(1)}}}}function V(ah){if(ah<32){return(ah<16?\"\\\\x0\":\"\\\\x\")+ah.toString(16)}var ai=String.fromCharCode(ah);return(ai===\"\\\\\"||ai===\"-\"||ai===\"]\"||ai===\"^\")?\"\\\\\"+ai:ai}function Z(an){var ar=an.substring(1,an.length-1).match(new RegExp(\"\\\\\\\\u[0-9A-Fa-f]{4}|\\\\\\\\x[0-9A-Fa-f]{2}|\\\\\\\\[0-3][0-7]{0,2}|\\\\\\\\[0-7]{1,2}|\\\\\\\\[\\\\s\\\\S]|-|[^-\\\\\\\\]\",\"g\"));var ah=[];var ap=ar[0]===\"^\";var ao=[\"[\"];if(ap){ao.push(\"^\")}for(var at=ap?1:0,al=ar.length;at<al;++at){var aj=ar[at];if(/\\\\[bdsw]/i.test(aj)){ao.push(aj)}else{var ai=ad(aj);var am;if(at+2<al&&\"-\"===ar[at+1]){am=ad(ar[at+2]);at+=2}else{am=ai}ah.push([ai,am]);if(!(am<65||ai>122)){if(!(am<65||ai>90)){ah.push([Math.max(65,ai)|32,Math.min(am,90)|32])}if(!(am<97||ai>122)){ah.push([Math.max(97,ai)&~32,Math.min(am,122)&~32])}}}}ah.sort(function(aw,av){return(aw[0]-av[0])||(av[1]-aw[1])});var ak=[];var aq=[];for(var at=0;at<ah.length;++at){var au=ah[at];if(au[0]<=aq[1]+1){aq[1]=Math.max(aq[1],au[1])}else{ak.push(aq=au)}}for(var at=0;at<ak.length;++at){var au=ak[at];ao.push(V(au[0]));if(au[1]>au[0]){if(au[1]+1>au[0]){ao.push(\"-\")}ao.push(V(au[1]))}}ao.push(\"]\");return ao.join(\"\")}function Y(an){var al=an.source.match(new RegExp(\"(?:\\\\[(?:[^\\\\x5C\\\\x5D]|\\\\\\\\[\\\\s\\\\S])*\\\\]|\\\\\\\\u[A-Fa-f0-9]{4}|\\\\\\\\x[A-Fa-f0-9]{2}|\\\\\\\\[0-9]+|\\\\\\\\[^ux0-9]|\\\\(\\\\?[:!=]|[\\\\(\\\\)\\\\^]|[^\\\\x5B\\\\x5C\\\\(\\\\)\\\\^]+)\",\"g\"));var aj=al.length;var ap=[];for(var am=0,ao=0;am<aj;++am){var ai=al[am];if(ai===\"(\"){++ao}else{if(\"\\\\\"===ai.charAt(0)){var ah=+ai.substring(1);if(ah){if(ah<=ao){ap[ah]=-1}else{al[am]=V(ah)}}}}}for(var am=1;am<ap.length;++am){if(-1===ap[am]){ap[am]=++af}}for(var am=0,ao=0;am<aj;++am){var ai=al[am];if(ai===\"(\"){++ao;if(!ap[ao]){al[am]=\"(?:\"}}else{if(\"\\\\\"===ai.charAt(0)){var ah=+ai.substring(1);if(ah&&ah<=ao){al[am]=\"\\\\\"+ap[ah]}}}}for(var am=0;am<aj;++am){if(\"^\"===al[am]&&\"^\"!==al[am+1]){al[am]=\"\"}}if(an.ignoreCase&&U){for(var am=0;am<aj;++am){var ai=al[am];var ak=ai.charAt(0);if(ai.length>=2&&ak===\"[\"){al[am]=Z(ai)}else{if(ak!==\"\\\\\"){al[am]=ai.replace(/[a-zA-Z]/g,function(aq){var ar=aq.charCodeAt(0);return\"[\"+String.fromCharCode(ar&~32,ar|32)+\"]\"})}}}}return al.join(\"\")}var ac=[];for(var X=0,W=ab.length;X<W;++X){var ag=ab[X];if(ag.global||ag.multiline){throw new Error(\"\"+ag)}ac.push(\"(?:\"+Y(ag)+\")\")}return new RegExp(ac.join(\"|\"),ae?\"gi\":\"g\")}function b(aa,Y){var W=/(?:^|\\s)nocode(?:\\s|$)/;var ab=[];var Z=0;var X=[];var V=0;function U(ac){switch(ac.nodeType){case 1:if(W.test(ac.className)){return}for(var af=ac.firstChild;af;af=af.nextSibling){U(af)}var ae=ac.nodeName.toLowerCase();if(\"br\"===ae||\"li\"===ae){ab[V]=\"\\n\";X[V<<1]=Z++;X[(V++<<1)|1]=ac}break;case 3:case 4:var ad=ac.nodeValue;if(ad.length){if(!Y){ad=ad.replace(/[ \\t\\r\\n]+/g,\" \")}else{ad=ad.replace(/\\r\\n?/g,\"\\n\")}ab[V]=ad;X[V<<1]=Z;Z+=ad.length;X[(V++<<1)|1]=ac}break}}U(aa);return{sourceCode:ab.join(\"\").replace(/\\n$/,\"\"),spans:X}}function C(U,W,Y,V){if(!W){return}var X={sourceCode:W,basePos:U};Y(X);V.push.apply(V,X.decorations)}var w=/\\S/;function p(U){var X=undefined;for(var W=U.firstChild;W;W=W.nextSibling){var V=W.nodeType;X=(V===1)?(X?U:W):(V===3)?(w.test(W.nodeValue)?U:X):X}return X===U?undefined:X}function h(W,V){var U={};var X;(function(){var af=W.concat(V);var aj=[];var ai={};for(var ad=0,ab=af.length;ad<ab;++ad){var aa=af[ad];var ae=aa[3];if(ae){for(var ag=ae.length;--ag>=0;){U[ae.charAt(ag)]=aa}}var ah=aa[1];var ac=\"\"+ah;if(!ai.hasOwnProperty(ac)){aj.push(ah);ai[ac]=null}}aj.push(/[\\0-\\uffff]/);X=l(aj)})();var Z=V.length;var Y=function(aj){var ab=aj.sourceCode,aa=aj.basePos;var af=[aa,G];var ah=0;var ap=ab.match(X)||[];var al={};for(var ag=0,at=ap.length;ag<at;++ag){var ai=ap[ag];var ar=al[ai];var ak=void 0;var ao;if(typeof ar===\"string\"){ao=false}else{var ac=U[ai.charAt(0)];if(ac){ak=ai.match(ac[1]);ar=ac[0]}else{for(var aq=0;aq<Z;++aq){ac=V[aq];ak=ai.match(ac[1]);if(ak){ar=ac[0];break}}if(!ak){ar=G}}ao=ar.length>=5&&\"lang-\"===ar.substring(0,5);if(ao&&!(ak&&typeof ak[1]===\"string\")){ao=false;ar=K}if(!ao){al[ai]=ar}}var ad=ah;ah+=ai.length;if(!ao){af.push(aa+ad,ar)}else{var an=ak[1];var am=ai.indexOf(an);var ae=am+an.length;if(ak[2]){ae=ai.length-ak[2].length;am=ae-an.length}var au=ar.substring(5);C(aa+ad,ai.substring(0,am),Y,af);C(aa+ad+am,an,r(au,an),af);C(aa+ad+ae,ai.substring(ae),Y,af)}}aj.decorations=af};return Y}function i(V){var Y=[],U=[];if(V.tripleQuotedStrings){Y.push([D,/^(?:\\'\\'\\'(?:[^\\'\\\\]|\\\\[\\s\\S]|\\'{1,2}(?=[^\\']))*(?:\\'\\'\\'|$)|\\\"\\\"\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S]|\\\"{1,2}(?=[^\\\"]))*(?:\\\"\\\"\\\"|$)|\\'(?:[^\\\\\\']|\\\\[\\s\\S])*(?:\\'|$)|\\\"(?:[^\\\\\\\"]|\\\\[\\s\\S])*(?:\\\"|$))/,null,\"'\\\"\"])}else{if(V.multiLineStrings){Y.push([D,/^(?:\\'(?:[^\\\\\\']|\\\\[\\s\\S])*(?:\\'|$)|\\\"(?:[^\\\\\\\"]|\\\\[\\s\\S])*(?:\\\"|$)|\\`(?:[^\\\\\\`]|\\\\[\\s\\S])*(?:\\`|$))/,null,\"'\\\"`\"])}else{Y.push([D,/^(?:\\'(?:[^\\\\\\'\\r\\n]|\\\\.)*(?:\\'|$)|\\\"(?:[^\\\\\\\"\\r\\n]|\\\\.)*(?:\\\"|$))/,null,\"\\\"'\"])}}if(V.verbatimStrings){U.push([D,/^@\\\"(?:[^\\\"]|\\\"\\\")*(?:\\\"|$)/,null])}var ab=V.hashComments;if(ab){if(V.cStyleComments){if(ab>1){Y.push([k,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,\"#\"])}else{Y.push([k,/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\\b|[^\\r\\n]*)/,null,\"#\"])}U.push([D,/^<(?:(?:(?:\\.\\.\\/)*|\\/?)(?:[\\w-]+(?:\\/[\\w-]+)+)?[\\w-]+\\.h(?:h|pp|\\+\\+)?|[a-z]\\w*)>/,null])}else{Y.push([k,/^#[^\\r\\n]*/,null,\"#\"])}}if(V.cStyleComments){U.push([k,/^\\/\\/[^\\r\\n]*/,null]);U.push([k,/^\\/\\*[\\s\\S]*?(?:\\*\\/|$)/,null])}if(V.regexLiterals){var aa=(\"/(?=[^/*])(?:[^/\\\\x5B\\\\x5C]|\\\\x5C[\\\\s\\\\S]|\\\\x5B(?:[^\\\\x5C\\\\x5D]|\\\\x5C[\\\\s\\\\S])*(?:\\\\x5D|$))+/\");U.push([\"lang-regex\",new RegExp(\"^\"+N+\"(\"+aa+\")\")])}var X=V.types;if(X){U.push([Q,X])}var W=(\"\"+V.keywords).replace(/^ | $/g,\"\");if(W.length){U.push([A,new RegExp(\"^(?:\"+W.replace(/[\\s,]+/g,\"|\")+\")\\\\b\"),null])}Y.push([G,/^\\s+/,null,\" \\r\\n\\t\\xA0\"]);var Z=/^.[^\\s\\w\\.$@\\'\\\"\\`\\/\\\\]*/;U.push([H,/^@[a-z_$][a-z_$@0-9]*/i,null],[Q,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\\w+_t\\b)/,null],[G,/^[a-z_$][a-z_$@0-9]*/i,null],[H,new RegExp(\"^(?:0x[a-f0-9]+|(?:\\\\d(?:_\\\\d+)*\\\\d*(?:\\\\.\\\\d*)?|\\\\.\\\\d\\\\+)(?:e[+\\\\-]?\\\\d+)?)[a-z]*\",\"i\"),null,\"0123456789\"],[G,/^\\\\[\\s\\S]?/,null],[M,Z,null]);return h(Y,U)}var L=i({keywords:B,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function S(W,ah,aa){var V=/(?:^|\\s)nocode(?:\\s|$)/;var ac=/\\r\\n?|\\n/;var ad=W.ownerDocument;var ag=ad.createElement(\"li\");while(W.firstChild){ag.appendChild(W.firstChild)}var X=[ag];function af(am){switch(am.nodeType){case 1:if(V.test(am.className)){break}if(\"br\"===am.nodeName){ae(am);if(am.parentNode){am.parentNode.removeChild(am)}}else{for(var ao=am.firstChild;ao;ao=ao.nextSibling){af(ao)}}break;case 3:case 4:if(aa){var an=am.nodeValue;var ak=an.match(ac);if(ak){var aj=an.substring(0,ak.index);am.nodeValue=aj;var ai=an.substring(ak.index+ak[0].length);if(ai){var al=am.parentNode;al.insertBefore(ad.createTextNode(ai),am.nextSibling)}ae(am);if(!aj){am.parentNode.removeChild(am)}}}break}}function ae(al){while(!al.nextSibling){al=al.parentNode;if(!al){return}}function aj(am,at){var ar=at?am.cloneNode(false):am;var ap=am.parentNode;if(ap){var aq=aj(ap,1);var ao=am.nextSibling;aq.appendChild(ar);for(var an=ao;an;an=ao){ao=an.nextSibling;aq.appendChild(an)}}return ar}var ai=aj(al.nextSibling,0);for(var ak;(ak=ai.parentNode)&&ak.nodeType===1;){ai=ak}X.push(ai)}for(var Z=0;Z<X.length;++Z){af(X[Z])}if(ah===(ah|0)){X[0].setAttribute(\"value\",ah)}var ab=ad.createElement(\"ol\");ab.className=\"linenums\";var Y=Math.max(0,((ah-1))|0)||0;for(var Z=0,U=X.length;Z<U;++Z){ag=X[Z];ag.className=\"L\"+((Z+Y)%10);if(!ag.firstChild){ag.appendChild(ad.createTextNode(\"\\xA0\"))}ab.appendChild(ag)}W.appendChild(ab)}function E(af){var X=/\\bMSIE\\s(\\d+)/.exec(navigator.userAgent);X=X&&+X[1]<=8;var ao=/\\n/g;var an=af.sourceCode;var ap=an.length;var Y=0;var ad=af.spans;var V=ad.length;var aj=0;var aa=af.decorations;var ab=aa.length;var ac=0;aa[ab]=ap;var av,at;for(at=av=0;at<ab;){if(aa[at]!==aa[at+2]){aa[av++]=aa[at++];aa[av++]=aa[at++]}else{at+=2}}ab=av;for(at=av=0;at<ab;){var aw=aa[at];var ae=aa[at+1];var Z=at+2;while(Z+2<=ab&&aa[Z+1]===ae){Z+=2}aa[av++]=aw;aa[av++]=ae;at=Z}ab=aa.length=av;var au=af.sourceNode;var ak;if(au){ak=au.style.display;au.style.display=\"none\"}try{var ah=null;while(aj<V){var ai=ad[aj];var U=ad[aj+2]||ap;var ar=aa[ac+2]||ap;var Z=Math.min(U,ar);var am=ad[aj+1];var W;if(am.nodeType!==1&&(W=an.substring(Y,Z))){if(X){W=W.replace(ao,\"\\r\")}am.nodeValue=W;var al=am.ownerDocument;var aq=al.createElement(\"span\");aq.className=aa[ac+1];var ag=am.parentNode;ag.replaceChild(aq,am);aq.appendChild(am);if(Y<U){ad[aj+1]=am=al.createTextNode(an.substring(Z,U));ag.insertBefore(am,aq.nextSibling)}}Y=Z;if(Y>=U){aj+=2}if(Y>=ar){ac+=2}}}finally{if(au){au.style.display=ak}}}var u={};function d(W,X){for(var U=X.length;--U>=0;){var V=X[U];if(!u.hasOwnProperty(V)){u[V]=W}else{if(O.console){console.warn(\"cannot override language handler %s\",V)}}}}function r(V,U){if(!(V&&u.hasOwnProperty(V))){V=/^\\s*</.test(U)?\"default-markup\":\"default-code\"}return u[V]}d(L,[\"default-code\"]);d(h([],[[G,/^[^<?]+/],[F,/^<!\\w[^>]*(?:>|$)/],[k,/^<\\!--[\\s\\S]*?(?:-\\->|$)/],[\"lang-\",/^<\\?([\\s\\S]+?)(?:\\?>|$)/],[\"lang-\",/^<%([\\s\\S]+?)(?:%>|$)/],[M,/^(?:<[%?]|[%?]>)/],[\"lang-\",/^<xmp\\b[^>]*>([\\s\\S]+?)<\\/xmp\\b[^>]*>/i],[\"lang-js\",/^<script\\b[^>]*>([\\s\\S]*?)(<\\/script\\b[^>]*>)/i],[\"lang-css\",/^<style\\b[^>]*>([\\s\\S]*?)(<\\/style\\b[^>]*>)/i],[\"lang-in.tag\",/^(<\\/?[a-z][^<>]*>)/i]]),[\"default-markup\",\"htm\",\"html\",\"mxml\",\"xhtml\",\"xml\",\"xsl\"]);d(h([[G,/^[\\s]+/,null,\" \\t\\r\\n\"],[o,/^(?:\\\"[^\\\"]*\\\"?|\\'[^\\']*\\'?)/,null,\"\\\"'\"]],[[n,/^^<\\/?[a-z](?:[\\w.:-]*\\w)?|\\/?>$/i],[R,/^(?!style[\\s=]|on)[a-z](?:[\\w:-]*\\w)?/i],[\"lang-uq.val\",/^=\\s*([^>\\'\\\"\\s]*(?:[^>\\'\\\"\\s\\/]|\\/(?=\\s)))/],[M,/^[=<>\\/]+/],[\"lang-js\",/^on\\w+\\s*=\\s*\\\"([^\\\"]+)\\\"/i],[\"lang-js\",/^on\\w+\\s*=\\s*\\'([^\\']+)\\'/i],[\"lang-js\",/^on\\w+\\s*=\\s*([^\\\"\\'>\\s]+)/i],[\"lang-css\",/^style\\s*=\\s*\\\"([^\\\"]+)\\\"/i],[\"lang-css\",/^style\\s*=\\s*\\'([^\\']+)\\'/i],[\"lang-css\",/^style\\s*=\\s*([^\\\"\\'>\\s]+)/i]]),[\"in.tag\"]);d(h([],[[o,/^[\\s\\S]+/]]),[\"uq.val\"]);d(i({keywords:m,hashComments:true,cStyleComments:true,types:f}),[\"c\",\"cc\",\"cpp\",\"cxx\",\"cyc\",\"m\"]);d(i({keywords:\"null,true,false\"}),[\"json\"]);d(i({keywords:T,hashComments:true,cStyleComments:true,verbatimStrings:true,types:f}),[\"cs\"]);d(i({keywords:y,cStyleComments:true}),[\"java\"]);d(i({keywords:I,hashComments:true,multiLineStrings:true}),[\"bsh\",\"csh\",\"sh\"]);d(i({keywords:J,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),[\"cv\",\"py\"]);d(i({keywords:t,hashComments:true,multiLineStrings:true,regexLiterals:true}),[\"perl\",\"pl\",\"pm\"]);d(i({keywords:g,hashComments:true,multiLineStrings:true,regexLiterals:true}),[\"rb\"]);d(i({keywords:x,cStyleComments:true,regexLiterals:true}),[\"js\"]);d(i({keywords:s,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),[\"coffee\"]);d(h([],[[D,/^[\\s\\S]+/]]),[\"regex\"]);function e(X){var W=X.langExtension;try{var U=b(X.sourceNode,X.pre);var V=U.sourceCode;X.sourceCode=V;X.spans=U.spans;X.basePos=0;r(W,V)(X);E(X)}catch(Y){if(O.console){console.log(Y&&Y.stack?Y.stack:Y)}}}function z(Y,X,W){var U=document.createElement(\"pre\");U.innerHTML=Y;if(W){S(U,W,true)}var V={langExtension:X,numberLines:W,sourceNode:U,pre:1};e(V);return U.innerHTML}function c(aj){function ab(al){return document.getElementsByTagName(al)}var ah=[ab(\"pre\"),ab(\"code\"),ab(\"xmp\")];var V=[];for(var ae=0;ae<ah.length;++ae){for(var ac=0,Y=ah[ae].length;ac<Y;++ac){V.push(ah[ae][ac])}}ah=null;var Z=Date;if(!Z.now){Z={now:function(){return +(new Date)}}}var aa=0;var U;var af=/\\blang(?:uage)?-([\\w.]+)(?!\\S)/;var ak=/\\bprettyprint\\b/;var W=/\\bprettyprinted\\b/;var ag=/pre|xmp/i;var ai=/^code$/i;var ad=/^(?:pre|code|xmp)$/i;function X(){var ar=(O.PR_SHOULD_USE_CONTINUATION?Z.now()+250:Infinity);for(;aa<V.length&&Z.now()<ar;aa++){var at=V[aa];var au=at.className;if(ak.test(au)&&!W.test(au)){var aw=false;for(var ao=at.parentNode;ao;ao=ao.parentNode){var ax=ao.tagName;if(ad.test(ax)&&ao.className&&ak.test(ao.className)){aw=true;break}}if(!aw){at.className+=\" prettyprinted\";var aq=au.match(af);var am;if(!aq&&(am=p(at))&&ai.test(am.tagName)){aq=am.className.match(af)}if(aq){aq=aq[1]}var ap;if(ag.test(at.tagName)){ap=1}else{var an=at.currentStyle;var al=(an?an.whiteSpace:(document.defaultView&&document.defaultView.getComputedStyle)?document.defaultView.getComputedStyle(at,null).getPropertyValue(\"white-space\"):0);ap=al&&\"pre\"===al.substring(0,3)}var av=at.className.match(/\\blinenums\\b(?::(\\d+))?/);av=av?av[1]&&av[1].length?+av[1]:true:false;if(av){S(at,av,ap)}U={langExtension:aq,sourceNode:at,numberLines:av,pre:ap};e(U)}}}if(aa<V.length){setTimeout(X,250)}else{if(aj){aj()}}}X()}var a=O.PR={createSimpleLexer:h,registerLangHandler:d,sourceDecorator:i,PR_ATTRIB_NAME:R,PR_ATTRIB_VALUE:o,PR_COMMENT:k,PR_DECLARATION:F,PR_KEYWORD:A,PR_LITERAL:H,PR_NOCODE:P,PR_PLAIN:G,PR_PUNCTUATION:M,PR_SOURCE:K,PR_STRING:D,PR_TAG:n,PR_TYPE:Q,prettyPrintOne:O.prettyPrintOne=z,prettyPrint:O.prettyPrint=c};if(typeof define===\"function\"&&define.amd){define(\"google-code-prettify\",[],function(){return a})}})();PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_DECLARATION,/^<!\\w[^>]*(?:>|$)/],[PR.PR_COMMENT,/^<\\!--[\\s\\S]*?(?:-\\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],[\"lang-\",/^<\\?([\\s\\S]+?)(?:\\?>|$)/],[\"lang-\",/^<%([\\s\\S]+?)(?:%>|$)/],[\"lang-\",/^<xmp\\b[^>]*>([\\s\\S]+?)<\\/xmp\\b[^>]*>/i],[\"lang-handlebars\",/^<script\\b[^>]*type\\s*=\\s*['\"]?text\\/x-handlebars-template['\"]?\\b[^>]*>([\\s\\S]*?)(<\\/script\\b[^>]*>)/i],[\"lang-js\",/^<script\\b[^>]*>([\\s\\S]*?)(<\\/script\\b[^>]*>)/i],[\"lang-css\",/^<style\\b[^>]*>([\\s\\S]*?)(<\\/style\\b[^>]*>)/i],[\"lang-in.tag\",/^(<\\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\\s*[\\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\\s*[\\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\\s*[\\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),[\"handlebars\",\"hbs\"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \\t\\r\\n\\f]+/,null,\" \\t\\r\\n\\f\"]],[[PR.PR_STRING,/^\\\"(?:[^\\n\\r\\f\\\\\\\"]|\\\\(?:\\r\\n?|\\n|\\f)|\\\\[\\s\\S])*\\\"/,null],[PR.PR_STRING,/^\\'(?:[^\\n\\r\\f\\\\\\']|\\\\(?:\\r\\n?|\\n|\\f)|\\\\[\\s\\S])*\\'/,null],[\"lang-css-str\",/^url\\(([^\\)\\\"\\']*)\\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\\!important|@import|@page|@media|@charset|inherit)(?=[^\\-\\w]|$)/i,null],[\"lang-css-kw\",/^(-?(?:[_a-z]|(?:\\\\[0-9a-f]+ ?))(?:[_a-z0-9\\-]|\\\\(?:\\\\[0-9a-f]+ ?))*)\\s*:/i],[PR.PR_COMMENT,/^\\/\\*[^*]*\\*+(?:[^\\/*][^*]*\\*+)*\\//],[PR.PR_COMMENT,/^(?:<!--|-->)/],[PR.PR_LITERAL,/^(?:\\d+|\\d*\\.\\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\\\[\\da-f]+ ?))(?:[_a-z\\d\\-]|\\\\(?:\\\\[\\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\\s\\w\\'\\\"]+/]]),[\"css\"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\\\[\\da-f]+ ?))(?:[_a-z\\d\\-]|\\\\(?:\\\\[\\da-f]+ ?))*/i]]),[\"css-kw\"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\\)\\\"\\']+/]]),[\"css-str\"]);"
  },
  {
    "path": "doc/classes/ContextMenu.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>ContextMenu</title>\n    <link rel=\"stylesheet\" href=\"http://yui.yahooapis.com/3.9.1/build/cssgrids/cssgrids-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/vendor/prettify/prettify-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/css/main.css\" id=\"site_styles\">\n    <link rel=\"icon\" href=\"../assets/favicon.ico\">\n    <script src=\"http://yui.yahooapis.com/combo?3.9.1/build/yui/yui-min.js\"></script>\n</head>\n<body class=\"yui3-skin-sam\">\n\n<div id=\"doc\">\n    <div id=\"hd\" class=\"yui3-g header\">\n        <div class=\"yui3-u-3-4\">\n                <h1><img src=\"../assets/css/logo.png\" title=\"\" width=\"117\" height=\"52\"></h1>\n        </div>\n        <div class=\"yui3-u-1-4 version\">\n            <em>API Docs for: </em>\n        </div>\n    </div>\n    <div id=\"bd\" class=\"yui3-g\">\n\n        <div class=\"yui3-u-1-4\">\n            <div id=\"docs-sidebar\" class=\"sidebar apidocs\">\n                <div id=\"api-list\">\n                    <h2 class=\"off-left\">APIs</h2>\n                    <div id=\"api-tabview\" class=\"tabview\">\n                        <ul class=\"tabs\">\n                            <li><a href=\"#api-classes\">Classes</a></li>\n                            <li><a href=\"#api-modules\">Modules</a></li>\n                        </ul>\n                \n                        <div id=\"api-tabview-filter\">\n                            <input type=\"search\" id=\"api-filter\" placeholder=\"Type to filter APIs\">\n                        </div>\n                \n                        <div id=\"api-tabview-panel\">\n                            <ul id=\"api-classes\" class=\"apis classes\">\n                                <li><a href=\"../classes/ContextMenu.html\">ContextMenu</a></li>\n                                <li><a href=\"../classes/LGraph.html\">LGraph</a></li>\n                                <li><a href=\"../classes/LGraphCanvas.html\">LGraphCanvas</a></li>\n                                <li><a href=\"../classes/LGraphNode.html\">LGraphNode</a></li>\n                                <li><a href=\"../classes/LiteGraph.html\">LiteGraph</a></li>\n                            </ul>\n                \n                \n                            <ul id=\"api-modules\" class=\"apis modules\">\n                            </ul>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <div class=\"yui3-u-3-4\">\n                <div id=\"api-options\">\n                    Show:\n                    <label for=\"api-show-inherited\">\n                        <input type=\"checkbox\" id=\"api-show-inherited\" checked>\n                        Inherited\n                    </label>\n            \n                    <label for=\"api-show-protected\">\n                        <input type=\"checkbox\" id=\"api-show-protected\">\n                        Protected\n                    </label>\n            \n                    <label for=\"api-show-private\">\n                        <input type=\"checkbox\" id=\"api-show-private\">\n                        Private\n                    </label>\n                    <label for=\"api-show-deprecated\">\n                        <input type=\"checkbox\" id=\"api-show-deprecated\">\n                        Deprecated\n                    </label>\n            \n                </div>\n            \n            <div class=\"apidocs\">\n                <div id=\"docs-main\">\n                    <div class=\"content\">\n<h1>ContextMenu Class</h1>\n<div class=\"box meta\">\n\n\n        <div class=\"foundat\">\n            Defined in: <a href=\"../files/.._src_litegraph.js.html#l8395\"><code>..&#x2F;src&#x2F;litegraph.js:8395</code></a>\n        </div>\n\n\n</div>\n\n\n<div class=\"box intro\">\n    <p>ContextMenu from LiteGUI</p>\n\n</div>\n\n    <div class=\"constructor\">\n        <h2>Constructor</h2>\n<div id=\"method_ContextMenu\" class=\"method item\">\n    <h3 class=\"name\"><code>ContextMenu</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>values</code>\n                </li>\n                <li class=\"arg\">\n                        <code>options</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l8395\"><code>..&#x2F;src&#x2F;litegraph.js:8395</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        \n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">values</code>\n                        <span class=\"type\">Array</span>\n\n\n                    <div class=\"param-description\">\n                        <p>(allows object { title: &quot;Nice text&quot;, callback: function ... })</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">options</code>\n                        <span class=\"type\">Object</span>\n\n\n                    <div class=\"param-description\">\n                        <p>[optional] Some options:\\</p>\n<ul>\n<li>title: title to show on top of the menu</li>\n<li>callback: function to call when an option is clicked, it receives the item information</li>\n<li>ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback</li>\n<li>event: you can pass a MouseEvent, this way the ContextMenu appears in that position</li>\n</ul>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n    </div>\n\n<div id=\"classdocs\" class=\"tabview\">\n    <ul class=\"api-class-tabs\">\n        <li class=\"api-class-tab index\"><a href=\"#index\">Index</a></li>\n\n    </ul>\n\n    <div>\n        <div id=\"index\" class=\"api-class-tabpanel index\">\n            <h2 class=\"off-left\">Item Index</h2>\n\n\n\n\n        </div>\n\n\n\n\n    </div>\n</div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n<script src=\"../assets/vendor/prettify/prettify-min.js\"></script>\n<script>prettyPrint();</script>\n<script src=\"../assets/js/yui-prettify.js\"></script>\n<script src=\"../assets/../api.js\"></script>\n<script src=\"../assets/js/api-filter.js\"></script>\n<script src=\"../assets/js/api-list.js\"></script>\n<script src=\"../assets/js/api-search.js\"></script>\n<script src=\"../assets/js/apidocs.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "doc/classes/LGraph.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>LGraph</title>\n    <link rel=\"stylesheet\" href=\"http://yui.yahooapis.com/3.9.1/build/cssgrids/cssgrids-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/vendor/prettify/prettify-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/css/main.css\" id=\"site_styles\">\n    <link rel=\"icon\" href=\"../assets/favicon.ico\">\n    <script src=\"http://yui.yahooapis.com/combo?3.9.1/build/yui/yui-min.js\"></script>\n</head>\n<body class=\"yui3-skin-sam\">\n\n<div id=\"doc\">\n    <div id=\"hd\" class=\"yui3-g header\">\n        <div class=\"yui3-u-3-4\">\n                <h1><img src=\"../assets/css/logo.png\" title=\"\" width=\"117\" height=\"52\"></h1>\n        </div>\n        <div class=\"yui3-u-1-4 version\">\n            <em>API Docs for: </em>\n        </div>\n    </div>\n    <div id=\"bd\" class=\"yui3-g\">\n\n        <div class=\"yui3-u-1-4\">\n            <div id=\"docs-sidebar\" class=\"sidebar apidocs\">\n                <div id=\"api-list\">\n                    <h2 class=\"off-left\">APIs</h2>\n                    <div id=\"api-tabview\" class=\"tabview\">\n                        <ul class=\"tabs\">\n                            <li><a href=\"#api-classes\">Classes</a></li>\n                            <li><a href=\"#api-modules\">Modules</a></li>\n                        </ul>\n                \n                        <div id=\"api-tabview-filter\">\n                            <input type=\"search\" id=\"api-filter\" placeholder=\"Type to filter APIs\">\n                        </div>\n                \n                        <div id=\"api-tabview-panel\">\n                            <ul id=\"api-classes\" class=\"apis classes\">\n                                <li><a href=\"../classes/ContextMenu.html\">ContextMenu</a></li>\n                                <li><a href=\"../classes/LGraph.html\">LGraph</a></li>\n                                <li><a href=\"../classes/LGraphCanvas.html\">LGraphCanvas</a></li>\n                                <li><a href=\"../classes/LGraphNode.html\">LGraphNode</a></li>\n                                <li><a href=\"../classes/LiteGraph.html\">LiteGraph</a></li>\n                            </ul>\n                \n                \n                            <ul id=\"api-modules\" class=\"apis modules\">\n                            </ul>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <div class=\"yui3-u-3-4\">\n                <div id=\"api-options\">\n                    Show:\n                    <label for=\"api-show-inherited\">\n                        <input type=\"checkbox\" id=\"api-show-inherited\" checked>\n                        Inherited\n                    </label>\n            \n                    <label for=\"api-show-protected\">\n                        <input type=\"checkbox\" id=\"api-show-protected\">\n                        Protected\n                    </label>\n            \n                    <label for=\"api-show-private\">\n                        <input type=\"checkbox\" id=\"api-show-private\">\n                        Private\n                    </label>\n                    <label for=\"api-show-deprecated\">\n                        <input type=\"checkbox\" id=\"api-show-deprecated\">\n                        Deprecated\n                    </label>\n            \n                </div>\n            \n            <div class=\"apidocs\">\n                <div id=\"docs-main\">\n                    <div class=\"content\">\n<h1>LGraph Class</h1>\n<div class=\"box meta\">\n\n\n        <div class=\"foundat\">\n            Defined in: <a href=\"../files/.._src_litegraph.js.html#l438\"><code>..&#x2F;src&#x2F;litegraph.js:438</code></a>\n        </div>\n\n\n</div>\n\n\n<div class=\"box intro\">\n    <p>LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.</p>\n\n</div>\n\n    <div class=\"constructor\">\n        <h2>Constructor</h2>\n<div id=\"method_LGraph\" class=\"method item\">\n    <h3 class=\"name\"><code>LGraph</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>o</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l438\"><code>..&#x2F;src&#x2F;litegraph.js:438</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        \n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">o</code>\n                        <span class=\"type\">Object</span>\n\n\n                    <div class=\"param-description\">\n                        <p>data from previous serialization [optional]</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n    </div>\n\n<div id=\"classdocs\" class=\"tabview\">\n    <ul class=\"api-class-tabs\">\n        <li class=\"api-class-tab index\"><a href=\"#index\">Index</a></li>\n\n            <li class=\"api-class-tab methods\"><a href=\"#methods\">Methods</a></li>\n    </ul>\n\n    <div>\n        <div id=\"index\" class=\"api-class-tabpanel index\">\n            <h2 class=\"off-left\">Item Index</h2>\n\n                <div class=\"index-section methods\">\n                    <h3>Methods</h3>\n\n                    <ul class=\"index-list methods\">\n                            <li class=\"index-item method\">\n                                <a href=\"#method_add\">add</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_addGlobalInput\">addGlobalInput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_addOutput\">addOutput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_arrange\">arrange</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_attachCanvas\">attachCanvas</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_changeInputType\">changeInputType</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_changeOutputType\">changeOutputType</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_clear\">clear</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_clearTriggeredSlots\">clearTriggeredSlots</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_configure\">configure</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_detachCanvas\">detachCanvas</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_findNodesByClass\">findNodesByClass</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_findNodesByTitle\">findNodesByTitle</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_findNodesByType\">findNodesByType</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getAncestors\">getAncestors</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getElapsedTime\">getElapsedTime</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getFixedTime\">getFixedTime</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getGroupOnPos\">getGroupOnPos</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getInputData\">getInputData</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getNodeById\">getNodeById</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getNodeOnPos\">getNodeOnPos</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getOutputData\">getOutputData</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getTime\">getTime</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_isLive\">isLive</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_remove\">remove</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_removeInput\">removeInput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_removeLink\">removeLink</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_removeOutput\">removeOutput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_renameInput\">renameInput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_renameOutput\">renameOutput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_runStep\">runStep</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_sendEventToAllNodes\">sendEventToAllNodes</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_serialize\">serialize</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_setGlobalInputData\">setGlobalInputData</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_setOutputData\">setOutputData</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_start\">start</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_stop execution\">stop execution</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_updateExecutionOrder\">updateExecutionOrder</a>\n\n                            </li>\n                    </ul>\n                </div>\n\n\n\n        </div>\n\n            <div id=\"methods\" class=\"api-class-tabpanel\">\n                <h2 class=\"off-left\">Methods</h2>\n\n<div id=\"method_add\" class=\"method item\">\n    <h3 class=\"name\"><code>add</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>node</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1004\"><code>..&#x2F;src&#x2F;litegraph.js:1004</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Adds a new node instasnce to this graph</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">node</code>\n                        <span class=\"type\"><a href=\"../classes/LGraphNode.html\" class=\"crosslink\">LGraphNode</a></span>\n\n\n                    <div class=\"param-description\">\n                        <p>the instance of the node</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_addGlobalInput\" class=\"method item\">\n    <h3 class=\"name\"><code>addGlobalInput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n                <li class=\"arg\">\n                        <code>value</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1251\"><code>..&#x2F;src&#x2F;litegraph.js:1251</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Tell this graph it has a global graph input of this type</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">value</code>\n                        <span class=\"type\"></span>\n\n\n                    <div class=\"param-description\">\n                        <p>[optional]</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_addOutput\" class=\"method item\">\n    <h3 class=\"name\"><code>addOutput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n                <li class=\"arg\">\n                        <code>value</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1375\"><code>..&#x2F;src&#x2F;litegraph.js:1375</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Creates a global graph output</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">value</code>\n                        <span class=\"type\"></span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_arrange\" class=\"method item\">\n    <h3 class=\"name\"><code>arrange</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l885\"><code>..&#x2F;src&#x2F;litegraph.js:885</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Positions every node in a more readable manner</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_attachCanvas\" class=\"method item\">\n    <h3 class=\"name\"><code>attachCanvas</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>graph_canvas</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l531\"><code>..&#x2F;src&#x2F;litegraph.js:531</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Attach Canvas to this graph</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">graph_canvas</code>\n                        <span class=\"type\">GraphCanvas</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_changeInputType\" class=\"method item\">\n    <h3 class=\"name\"><code>changeInputType</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1333\"><code>..&#x2F;src&#x2F;litegraph.js:1333</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Changes the type of a global graph input</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_changeOutputType\" class=\"method item\">\n    <h3 class=\"name\"><code>changeOutputType</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1450\"><code>..&#x2F;src&#x2F;litegraph.js:1450</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Changes the type of a global graph output</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_clear\" class=\"method item\">\n    <h3 class=\"name\"><code>clear</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l468\"><code>..&#x2F;src&#x2F;litegraph.js:468</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Removes all nodes from this graph</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_clearTriggeredSlots\" class=\"method item\">\n    <h3 class=\"name\"><code>clearTriggeredSlots</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1533\"><code>..&#x2F;src&#x2F;litegraph.js:1533</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>clears the triggered slot animation in all links (stop visual animation)</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_configure\" class=\"method item\">\n    <h3 class=\"name\"><code>configure</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>str</code>\n                </li>\n                <li class=\"arg\">\n                        <code>returns</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1619\"><code>..&#x2F;src&#x2F;litegraph.js:1619</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Configure a graph from a JSON string</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">str</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>configure a graph from a JSON string</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">returns</code>\n                        <span class=\"type\">Boolean</span>\n\n\n                    <div class=\"param-description\">\n                        <p>if there was any error parsing</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_detachCanvas\" class=\"method item\">\n    <h3 class=\"name\"><code>detachCanvas</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>graph_canvas</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l550\"><code>..&#x2F;src&#x2F;litegraph.js:550</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Detach Canvas from this graph</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">graph_canvas</code>\n                        <span class=\"type\">GraphCanvas</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_findNodesByClass\" class=\"method item\">\n    <h3 class=\"name\"><code>findNodesByClass</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>classObject</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Array</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1162\"><code>..&#x2F;src&#x2F;litegraph.js:1162</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns a list of nodes that matches a class</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">classObject</code>\n                        <span class=\"type\">Class</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the class itself (not an string)</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Array</span>:\n                    <p>a list with all the nodes of this type</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_findNodesByTitle\" class=\"method item\">\n    <h3 class=\"name\"><code>findNodesByTitle</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Array</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1195\"><code>..&#x2F;src&#x2F;litegraph.js:1195</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns a list of nodes that matches a name</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the name of the node to search</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Array</span>:\n                    <p>a list with all the nodes with this name</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_findNodesByType\" class=\"method item\">\n    <h3 class=\"name\"><code>findNodesByType</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Array</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1178\"><code>..&#x2F;src&#x2F;litegraph.js:1178</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns a list of nodes that matches a type</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the name of the node type</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Array</span>:\n                    <p>a list with all the nodes of this type</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getAncestors\" class=\"method item\">\n    <h3 class=\"name\"><code>getAncestors</code></h3>\n\n        <span class=\"paren\">()</span>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Array</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l848\"><code>..&#x2F;src&#x2F;litegraph.js:848</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.\nIt doesnt include the node itself</p>\n\n    </div>\n\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Array</span>:\n                    <p>an array with all the LGraphNodes that affect this node, in order of execution</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getElapsedTime\" class=\"method item\">\n    <h3 class=\"name\"><code>getElapsedTime</code></h3>\n\n        <span class=\"paren\">()</span>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Number</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l950\"><code>..&#x2F;src&#x2F;litegraph.js:950</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct\nif the nodes are using graphical actions</p>\n\n    </div>\n\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Number</span>:\n                    <p>number of milliseconds it took the last cycle</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getFixedTime\" class=\"method item\">\n    <h3 class=\"name\"><code>getFixedTime</code></h3>\n\n        <span class=\"paren\">()</span>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Number</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l939\"><code>..&#x2F;src&#x2F;litegraph.js:939</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant</p>\n\n    </div>\n\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Number</span>:\n                    <p>number of milliseconds the graph has been running</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getGroupOnPos\" class=\"method item\">\n    <h3 class=\"name\"><code>getGroupOnPos</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>x</code>\n                </li>\n                <li class=\"arg\">\n                        <code>y</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">LGraphGroup</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1231\"><code>..&#x2F;src&#x2F;litegraph.js:1231</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns the top-most group in that position</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">x</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the x coordinate in canvas space</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">y</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the y coordinate in canvas space</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">LGraphGroup</span>:\n                    <p>the group or null</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getInputData\" class=\"method item\">\n    <h3 class=\"name\"><code>getInputData</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\"></span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1288\"><code>..&#x2F;src&#x2F;litegraph.js:1288</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns the current value of a global graph input</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\"></span>:\n                    <p>the data</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getNodeById\" class=\"method item\">\n    <h3 class=\"name\"><code>getNodeById</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>id</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1149\"><code>..&#x2F;src&#x2F;litegraph.js:1149</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns a node by its id.</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">id</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_getNodeOnPos\" class=\"method item\">\n    <h3 class=\"name\"><code>getNodeOnPos</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>x</code>\n                </li>\n                <li class=\"arg\">\n                        <code>y</code>\n                </li>\n                <li class=\"arg\">\n                        <code>nodes_list</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\"><a href=\"../classes/LGraphNode.html\" class=\"crosslink\">LGraphNode</a></span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1211\"><code>..&#x2F;src&#x2F;litegraph.js:1211</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns the top-most node in this position of the canvas</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">x</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the x coordinate in canvas space</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">y</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the y coordinate in canvas space</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">nodes_list</code>\n                        <span class=\"type\">Array</span>\n\n\n                    <div class=\"param-description\">\n                        <p>a list with all the nodes to search from, by default is all the nodes in the graph</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\"><a href=\"../classes/LGraphNode.html\" class=\"crosslink\">LGraphNode</a></span>:\n                    <p>the node at this position or null</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getOutputData\" class=\"method item\">\n    <h3 class=\"name\"><code>getOutputData</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\"></span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1408\"><code>..&#x2F;src&#x2F;litegraph.js:1408</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns the current value of a global graph output</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\"></span>:\n                    <p>the data</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getTime\" class=\"method item\">\n    <h3 class=\"name\"><code>getTime</code></h3>\n\n        <span class=\"paren\">()</span>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Number</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l929\"><code>..&#x2F;src&#x2F;litegraph.js:929</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns the amount of time the graph has been running in milliseconds</p>\n\n    </div>\n\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Number</span>:\n                    <p>number of milliseconds the graph has been running</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_isLive\" class=\"method item\">\n    <h3 class=\"name\"><code>isLive</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1514\"><code>..&#x2F;src&#x2F;litegraph.js:1514</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>returns if the graph is in live mode</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_remove\" class=\"method item\">\n    <h3 class=\"name\"><code>remove</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>node</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1068\"><code>..&#x2F;src&#x2F;litegraph.js:1068</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Removes a node from the graph</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">node</code>\n                        <span class=\"type\"><a href=\"../classes/LGraphNode.html\" class=\"crosslink\">LGraphNode</a></span>\n\n\n                    <div class=\"param-description\">\n                        <p>the instance of the node</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_removeInput\" class=\"method item\">\n    <h3 class=\"name\"><code>removeInput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1353\"><code>..&#x2F;src&#x2F;litegraph.js:1353</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Removes a global graph input</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_removeLink\" class=\"method item\">\n    <h3 class=\"name\"><code>removeLink</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>link_id</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1565\"><code>..&#x2F;src&#x2F;litegraph.js:1565</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Destroys a link</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">link_id</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_removeOutput\" class=\"method item\">\n    <h3 class=\"name\"><code>removeOutput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1470\"><code>..&#x2F;src&#x2F;litegraph.js:1470</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Removes a global graph output</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_renameInput\" class=\"method item\">\n    <h3 class=\"name\"><code>renameInput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>old_name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>new_name</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1302\"><code>..&#x2F;src&#x2F;litegraph.js:1302</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Changes the name of a global graph input</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">old_name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">new_name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_renameOutput\" class=\"method item\">\n    <h3 class=\"name\"><code>renameOutput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>old_name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>new_name</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1422\"><code>..&#x2F;src&#x2F;litegraph.js:1422</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Renames a global graph output</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">old_name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">new_name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_runStep\" class=\"method item\">\n    <h3 class=\"name\"><code>runStep</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>num</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l634\"><code>..&#x2F;src&#x2F;litegraph.js:634</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Run N steps (cycles) of the graph</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">num</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>number of steps to run, default is 1</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_sendEventToAllNodes\" class=\"method item\">\n    <h3 class=\"name\"><code>sendEventToAllNodes</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>eventname</code>\n                </li>\n                <li class=\"arg\">\n                        <code>params</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l962\"><code>..&#x2F;src&#x2F;litegraph.js:962</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Sends an event to all the nodes, useful to trigger stuff</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">eventname</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the name of the event (function to be called)</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">params</code>\n                        <span class=\"type\">Array</span>\n\n\n                    <div class=\"param-description\">\n                        <p>parameters in array format</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_serialize\" class=\"method item\">\n    <h3 class=\"name\"><code>serialize</code></h3>\n\n        <span class=\"paren\">()</span>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Object</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1582\"><code>..&#x2F;src&#x2F;litegraph.js:1582</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Creates a Object containing all the info about this graph, it can be serialized</p>\n\n    </div>\n\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Object</span>:\n                    <p>value of the node</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_setGlobalInputData\" class=\"method item\">\n    <h3 class=\"name\"><code>setGlobalInputData</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>data</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1274\"><code>..&#x2F;src&#x2F;litegraph.js:1274</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Assign a data to the global graph input</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">data</code>\n                        <span class=\"type\"></span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_setOutputData\" class=\"method item\">\n    <h3 class=\"name\"><code>setOutputData</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>value</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1394\"><code>..&#x2F;src&#x2F;litegraph.js:1394</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Assign a data to the global output</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">value</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_start\" class=\"method item\">\n    <h3 class=\"name\"><code>start</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>interval</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l567\"><code>..&#x2F;src&#x2F;litegraph.js:567</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Starts running this graph every interval milliseconds.</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">interval</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_stop execution\" class=\"method item\">\n    <h3 class=\"name\"><code>stop execution</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l609\"><code>..&#x2F;src&#x2F;litegraph.js:609</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Stops the execution loop of the graph</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_updateExecutionOrder\" class=\"method item\">\n    <h3 class=\"name\"><code>updateExecutionOrder</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l716\"><code>..&#x2F;src&#x2F;litegraph.js:716</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than\nnodes with only inputs.</p>\n\n    </div>\n\n\n\n\n</div>\n            </div>\n\n\n\n    </div>\n</div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n<script src=\"../assets/vendor/prettify/prettify-min.js\"></script>\n<script>prettyPrint();</script>\n<script src=\"../assets/js/yui-prettify.js\"></script>\n<script src=\"../assets/../api.js\"></script>\n<script src=\"../assets/js/api-filter.js\"></script>\n<script src=\"../assets/js/api-list.js\"></script>\n<script src=\"../assets/js/api-search.js\"></script>\n<script src=\"../assets/js/apidocs.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "doc/classes/LGraphCanvas.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>LGraphCanvas</title>\n    <link rel=\"stylesheet\" href=\"http://yui.yahooapis.com/3.9.1/build/cssgrids/cssgrids-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/vendor/prettify/prettify-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/css/main.css\" id=\"site_styles\">\n    <link rel=\"icon\" href=\"../assets/favicon.ico\">\n    <script src=\"http://yui.yahooapis.com/combo?3.9.1/build/yui/yui-min.js\"></script>\n</head>\n<body class=\"yui3-skin-sam\">\n\n<div id=\"doc\">\n    <div id=\"hd\" class=\"yui3-g header\">\n        <div class=\"yui3-u-3-4\">\n                <h1><img src=\"../assets/css/logo.png\" title=\"\" width=\"117\" height=\"52\"></h1>\n        </div>\n        <div class=\"yui3-u-1-4 version\">\n            <em>API Docs for: </em>\n        </div>\n    </div>\n    <div id=\"bd\" class=\"yui3-g\">\n\n        <div class=\"yui3-u-1-4\">\n            <div id=\"docs-sidebar\" class=\"sidebar apidocs\">\n                <div id=\"api-list\">\n                    <h2 class=\"off-left\">APIs</h2>\n                    <div id=\"api-tabview\" class=\"tabview\">\n                        <ul class=\"tabs\">\n                            <li><a href=\"#api-classes\">Classes</a></li>\n                            <li><a href=\"#api-modules\">Modules</a></li>\n                        </ul>\n                \n                        <div id=\"api-tabview-filter\">\n                            <input type=\"search\" id=\"api-filter\" placeholder=\"Type to filter APIs\">\n                        </div>\n                \n                        <div id=\"api-tabview-panel\">\n                            <ul id=\"api-classes\" class=\"apis classes\">\n                                <li><a href=\"../classes/ContextMenu.html\">ContextMenu</a></li>\n                                <li><a href=\"../classes/LGraph.html\">LGraph</a></li>\n                                <li><a href=\"../classes/LGraphCanvas.html\">LGraphCanvas</a></li>\n                                <li><a href=\"../classes/LGraphNode.html\">LGraphNode</a></li>\n                                <li><a href=\"../classes/LiteGraph.html\">LiteGraph</a></li>\n                            </ul>\n                \n                \n                            <ul id=\"api-modules\" class=\"apis modules\">\n                            </ul>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <div class=\"yui3-u-3-4\">\n                <div id=\"api-options\">\n                    Show:\n                    <label for=\"api-show-inherited\">\n                        <input type=\"checkbox\" id=\"api-show-inherited\" checked>\n                        Inherited\n                    </label>\n            \n                    <label for=\"api-show-protected\">\n                        <input type=\"checkbox\" id=\"api-show-protected\">\n                        Protected\n                    </label>\n            \n                    <label for=\"api-show-private\">\n                        <input type=\"checkbox\" id=\"api-show-private\">\n                        Private\n                    </label>\n                    <label for=\"api-show-deprecated\">\n                        <input type=\"checkbox\" id=\"api-show-deprecated\">\n                        Deprecated\n                    </label>\n            \n                </div>\n            \n            <div class=\"apidocs\">\n                <div id=\"docs-main\">\n                    <div class=\"content\">\n<h1>LGraphCanvas Class</h1>\n<div class=\"box meta\">\n\n\n        <div class=\"foundat\">\n            Defined in: <a href=\"../files/.._src_litegraph.js.html#l4164\"><code>..&#x2F;src&#x2F;litegraph.js:4164</code></a>\n        </div>\n\n\n</div>\n\n\n<div class=\"box intro\">\n    <p>marks as dirty the canvas, this way it will be rendered again</p>\n\n</div>\n\n    <div class=\"constructor\">\n        <h2>Constructor</h2>\n<div id=\"method_LGraphCanvas\" class=\"method item\">\n    <h3 class=\"name\"><code>LGraphCanvas</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>canvas</code>\n                </li>\n                <li class=\"arg\">\n                        <code>graph</code>\n                </li>\n                <li class=\"arg\">\n                        <code>options</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4164\"><code>..&#x2F;src&#x2F;litegraph.js:4164</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        \n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">canvas</code>\n                        <span class=\"type\">HTMLCanvas</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the canvas where you want to render (it accepts a selector in string format or the canvas element itself)</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">graph</code>\n                        <span class=\"type\"><a href=\"../classes/LGraph.html\" class=\"crosslink\">LGraph</a></span>\n\n\n                    <div class=\"param-description\">\n                        <p>[optional]</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">options</code>\n                        <span class=\"type\">Object</span>\n\n\n                    <div class=\"param-description\">\n                        <p>[optional] { skip_rendering, autoresize }</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n    </div>\n\n<div id=\"classdocs\" class=\"tabview\">\n    <ul class=\"api-class-tabs\">\n        <li class=\"api-class-tab index\"><a href=\"#index\">Index</a></li>\n\n            <li class=\"api-class-tab methods\"><a href=\"#methods\">Methods</a></li>\n    </ul>\n\n    <div>\n        <div id=\"index\" class=\"api-class-tabpanel index\">\n            <h2 class=\"off-left\">Item Index</h2>\n\n                <div class=\"index-section methods\">\n                    <h3>Methods</h3>\n\n                    <ul class=\"index-list methods\">\n                            <li class=\"index-item method\">\n                                <a href=\"#method_adjustMouseEvent\">adjustMouseEvent</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_bindEvents\">bindEvents</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_bringToFront\">bringToFront</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_centerOnNode\">centerOnNode</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_clear\">clear</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_closeSubgraph\">closeSubgraph</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_computeVisibleNodes\">computeVisibleNodes</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_convertCanvasToOffset\">convertCanvasToOffset</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_convertOffsetToCanvas\">convertOffsetToCanvas</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_deleteSelectedNodes\">deleteSelectedNodes</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_deselectAllNodes\">deselectAllNodes</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_deselectNode\">deselectNode</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_draw\">draw</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_drawBackCanvas\">drawBackCanvas</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_drawConnections\">drawConnections</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_drawFrontCanvas\">drawFrontCanvas</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_drawGroups\">drawGroups</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_drawNode\">drawNode</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_drawNodeShape\">drawNodeShape</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_drawNodeWidgets\">drawNodeWidgets</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_enableWebGL\">enableWebGL</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getCanvasWindow\">getCanvasWindow</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_isOverNodeBox\">isOverNodeBox</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_isOverNodeInput\">isOverNodeInput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_openSubgraph\">openSubgraph</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_processDrop\">processDrop</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_processKey\">processKey</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_processMouseMove\">processMouseMove</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_processMouseUp\">processMouseUp</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_processMouseWheel\">processMouseWheel</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_processNodeWidgets\">processNodeWidgets</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_renderInfo\">renderInfo</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_renderLink\">renderLink</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_resize\">resize</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_selectNode\">selectNode</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_selectNodes\">selectNodes</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_sendToBack\">sendToBack</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_setCanvas\">setCanvas</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_setGraph\">setGraph</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_setZoom\">setZoom</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_startRendering\">startRendering</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_stopRendering\">stopRendering</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_switchLiveMode\">switchLiveMode</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_unbindEvents\">unbindEvents</a>\n\n                            </li>\n                    </ul>\n                </div>\n\n\n\n        </div>\n\n            <div id=\"methods\" class=\"api-class-tabpanel\">\n                <h2 class=\"off-left\">Methods</h2>\n\n<div id=\"method_adjustMouseEvent\" class=\"method item\">\n    <h3 class=\"name\"><code>adjustMouseEvent</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5319\"><code>..&#x2F;src&#x2F;litegraph.js:5319</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>adds some useful properties to a mouse event, like the position in graph coordinates</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_bindEvents\" class=\"method item\">\n    <h3 class=\"name\"><code>bindEvents</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4038\"><code>..&#x2F;src&#x2F;litegraph.js:4038</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>binds mouse, keyboard, touch and drag events to the canvas</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_bringToFront\" class=\"method item\">\n    <h3 class=\"name\"><code>bringToFront</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5403\"><code>..&#x2F;src&#x2F;litegraph.js:5403</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>brings a node to front (above all other nodes)</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_centerOnNode\" class=\"method item\">\n    <h3 class=\"name\"><code>centerOnNode</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5308\"><code>..&#x2F;src&#x2F;litegraph.js:5308</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>centers the camera on a given node</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_clear\" class=\"method item\">\n    <h3 class=\"name\"><code>clear</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l3837\"><code>..&#x2F;src&#x2F;litegraph.js:3837</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>clears all the data inside</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_closeSubgraph\" class=\"method item\">\n    <h3 class=\"name\"><code>closeSubgraph</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>assigns</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l3938\"><code>..&#x2F;src&#x2F;litegraph.js:3938</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>closes a subgraph contained inside a node</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">assigns</code>\n                        <span class=\"type\"><a href=\"../classes/LGraph.html\" class=\"crosslink\">LGraph</a></span>\n\n\n                    <div class=\"param-description\">\n                        <p>a graph</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_computeVisibleNodes\" class=\"method item\">\n    <h3 class=\"name\"><code>computeVisibleNodes</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5436\"><code>..&#x2F;src&#x2F;litegraph.js:5436</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>checks which nodes are visible (inside the camera area)</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_convertCanvasToOffset\" class=\"method item\">\n    <h3 class=\"name\"><code>convertCanvasToOffset</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5387\"><code>..&#x2F;src&#x2F;litegraph.js:5387</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>converts a coordinate from Canvas2D coordinates to graph space</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_convertOffsetToCanvas\" class=\"method item\">\n    <h3 class=\"name\"><code>convertOffsetToCanvas</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5378\"><code>..&#x2F;src&#x2F;litegraph.js:5378</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>converts a coordinate from graph coordinates to canvas2D coordinates</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_deleteSelectedNodes\" class=\"method item\">\n    <h3 class=\"name\"><code>deleteSelectedNodes</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5291\"><code>..&#x2F;src&#x2F;litegraph.js:5291</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>deletes all nodes in the current selection from the graph</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_deselectAllNodes\" class=\"method item\">\n    <h3 class=\"name\"><code>deselectAllNodes</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5268\"><code>..&#x2F;src&#x2F;litegraph.js:5268</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>removes all nodes from the current selection</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_deselectNode\" class=\"method item\">\n    <h3 class=\"name\"><code>deselectNode</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5242\"><code>..&#x2F;src&#x2F;litegraph.js:5242</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>removes a node from the current selection</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_draw\" class=\"method item\">\n    <h3 class=\"name\"><code>draw</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5461\"><code>..&#x2F;src&#x2F;litegraph.js:5461</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_drawBackCanvas\" class=\"method item\">\n    <h3 class=\"name\"><code>drawBackCanvas</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5659\"><code>..&#x2F;src&#x2F;litegraph.js:5659</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>draws the back canvas (the one containing the background and the connections)</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_drawConnections\" class=\"method item\">\n    <h3 class=\"name\"><code>drawConnections</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l6311\"><code>..&#x2F;src&#x2F;litegraph.js:6311</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>draws every connection visible in the canvas\nOPTIMIZE THIS: precatch connections position instead of recomputing them every time</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_drawFrontCanvas\" class=\"method item\">\n    <h3 class=\"name\"><code>drawFrontCanvas</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5488\"><code>..&#x2F;src&#x2F;litegraph.js:5488</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>draws the front canvas (the one containing all the nodes)</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_drawGroups\" class=\"method item\">\n    <h3 class=\"name\"><code>drawGroups</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l6924\"><code>..&#x2F;src&#x2F;litegraph.js:6924</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>draws every group area in the background</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_drawNode\" class=\"method item\">\n    <h3 class=\"name\"><code>drawNode</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5807\"><code>..&#x2F;src&#x2F;litegraph.js:5807</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>draws the given node inside the canvas</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_drawNodeShape\" class=\"method item\">\n    <h3 class=\"name\"><code>drawNodeShape</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l6115\"><code>..&#x2F;src&#x2F;litegraph.js:6115</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>draws the shape of the given node in the canvas</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_drawNodeWidgets\" class=\"method item\">\n    <h3 class=\"name\"><code>drawNodeWidgets</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l6663\"><code>..&#x2F;src&#x2F;litegraph.js:6663</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>draws the widgets stored inside a node</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_enableWebGL\" class=\"method item\">\n    <h3 class=\"name\"><code>enableWebGL</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4138\"><code>..&#x2F;src&#x2F;litegraph.js:4138</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>this function allows to render the canvas using WebGL instead of Canvas2D\nthis is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_getCanvasWindow\" class=\"method item\">\n    <h3 class=\"name\"><code>getCanvasWindow</code></h3>\n\n        <span class=\"paren\">()</span>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Window</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4180\"><code>..&#x2F;src&#x2F;litegraph.js:4180</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Used to attach the canvas in a popup</p>\n\n    </div>\n\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Window</span>:\n                    <p>returns the window where the canvas is attached (the DOM root node)</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_isOverNodeBox\" class=\"method item\">\n    <h3 class=\"name\"><code>isOverNodeBox</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4876\"><code>..&#x2F;src&#x2F;litegraph.js:4876</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>retuns true if a position (in graph space) is on top of a node little corner box</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_isOverNodeInput\" class=\"method item\">\n    <h3 class=\"name\"><code>isOverNodeInput</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4888\"><code>..&#x2F;src&#x2F;litegraph.js:4888</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>retuns true if a position (in graph space) is on top of a node input slot</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_openSubgraph\" class=\"method item\">\n    <h3 class=\"name\"><code>openSubgraph</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>graph</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l3911\"><code>..&#x2F;src&#x2F;litegraph.js:3911</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>opens a graph contained inside a node in the current graph</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">graph</code>\n                        <span class=\"type\"><a href=\"../classes/LGraph.html\" class=\"crosslink\">LGraph</a></span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_processDrop\" class=\"method item\">\n    <h3 class=\"name\"><code>processDrop</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5074\"><code>..&#x2F;src&#x2F;litegraph.js:5074</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>process a item drop event on top the canvas</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_processKey\" class=\"method item\">\n    <h3 class=\"name\"><code>processKey</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4917\"><code>..&#x2F;src&#x2F;litegraph.js:4917</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>process a key event</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_processMouseMove\" class=\"method item\">\n    <h3 class=\"name\"><code>processMouseMove</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4501\"><code>..&#x2F;src&#x2F;litegraph.js:4501</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Called when a mouse move event has to be processed</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_processMouseUp\" class=\"method item\">\n    <h3 class=\"name\"><code>processMouseUp</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4675\"><code>..&#x2F;src&#x2F;litegraph.js:4675</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Called when a mouse up event has to be processed</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_processMouseWheel\" class=\"method item\">\n    <h3 class=\"name\"><code>processMouseWheel</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4847\"><code>..&#x2F;src&#x2F;litegraph.js:4847</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Called when a mouse wheel event has to be processed</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_processNodeWidgets\" class=\"method item\">\n    <h3 class=\"name\"><code>processNodeWidgets</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l6813\"><code>..&#x2F;src&#x2F;litegraph.js:6813</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>process an event on widgets</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_renderInfo\" class=\"method item\">\n    <h3 class=\"name\"><code>renderInfo</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5632\"><code>..&#x2F;src&#x2F;litegraph.js:5632</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>draws some useful stats in the corner of the canvas</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_renderLink\" class=\"method item\">\n    <h3 class=\"name\"><code>renderLink</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>a</code>\n                </li>\n                <li class=\"arg\">\n                        <code>b</code>\n                </li>\n                <li class=\"arg\">\n                        <code>link</code>\n                </li>\n                <li class=\"arg\">\n                        <code>skip_border</code>\n                </li>\n                <li class=\"arg\">\n                        <code>flow</code>\n                </li>\n                <li class=\"arg\">\n                        <code>color</code>\n                </li>\n                <li class=\"arg\">\n                        <code>start_dir</code>\n                </li>\n                <li class=\"arg\">\n                        <code>end_dir</code>\n                </li>\n                <li class=\"arg\">\n                        <code>num_sublines</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l6398\"><code>..&#x2F;src&#x2F;litegraph.js:6398</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>draws a link between two points</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">a</code>\n                        <span class=\"type\">Vec2</span>\n\n\n                    <div class=\"param-description\">\n                        <p>start pos</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">b</code>\n                        <span class=\"type\">Vec2</span>\n\n\n                    <div class=\"param-description\">\n                        <p>end pos</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">link</code>\n                        <span class=\"type\">Object</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the link object with all the link info</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">skip_border</code>\n                        <span class=\"type\">Boolean</span>\n\n\n                    <div class=\"param-description\">\n                        <p>ignore the shadow of the link</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">flow</code>\n                        <span class=\"type\">Boolean</span>\n\n\n                    <div class=\"param-description\">\n                        <p>show flow animation (for events)</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">color</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the color for the link</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">start_dir</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the direction enum</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">end_dir</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the direction enum</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">num_sublines</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>number of sublines (useful to represent vec3 or rgb)</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_resize\" class=\"method item\">\n    <h3 class=\"name\"><code>resize</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l6979\"><code>..&#x2F;src&#x2F;litegraph.js:6979</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_selectNode\" class=\"method item\">\n    <h3 class=\"name\"><code>selectNode</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5192\"><code>..&#x2F;src&#x2F;litegraph.js:5192</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>selects a given node (or adds it to the current selection)</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_selectNodes\" class=\"method item\">\n    <h3 class=\"name\"><code>selectNodes</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5204\"><code>..&#x2F;src&#x2F;litegraph.js:5204</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>selects several nodes (or adds them to the current selection)</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_sendToBack\" class=\"method item\">\n    <h3 class=\"name\"><code>sendToBack</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5416\"><code>..&#x2F;src&#x2F;litegraph.js:5416</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>sends a node to the back (below all other nodes)</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_setCanvas\" class=\"method item\">\n    <h3 class=\"name\"><code>setCanvas</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>assigns</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l3961\"><code>..&#x2F;src&#x2F;litegraph.js:3961</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>assigns a canvas</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">assigns</code>\n                        <span class=\"type\">Canvas</span>\n\n\n                    <div class=\"param-description\">\n                        <p>a canvas (also accepts the ID of the element (not a selector)</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_setGraph\" class=\"method item\">\n    <h3 class=\"name\"><code>setGraph</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>graph</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l3880\"><code>..&#x2F;src&#x2F;litegraph.js:3880</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>assigns a graph, you can reasign graphs to the same canvas</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">graph</code>\n                        <span class=\"type\"><a href=\"../classes/LGraph.html\" class=\"crosslink\">LGraph</a></span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_setZoom\" class=\"method item\">\n    <h3 class=\"name\"><code>setZoom</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l5347\"><code>..&#x2F;src&#x2F;litegraph.js:5347</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_startRendering\" class=\"method item\">\n    <h3 class=\"name\"><code>startRendering</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4194\"><code>..&#x2F;src&#x2F;litegraph.js:4194</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>starts rendering the content of the canvas when needed</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_stopRendering\" class=\"method item\">\n    <h3 class=\"name\"><code>stopRendering</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4218\"><code>..&#x2F;src&#x2F;litegraph.js:4218</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>stops rendering the content of the canvas (to save resources)</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_switchLiveMode\" class=\"method item\">\n    <h3 class=\"name\"><code>switchLiveMode</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l7002\"><code>..&#x2F;src&#x2F;litegraph.js:7002</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>switches to live mode (node shapes are not rendered, only the content)\nthis feature was designed when graphs where meant to create user interfaces</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_unbindEvents\" class=\"method item\">\n    <h3 class=\"name\"><code>unbindEvents</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l4090\"><code>..&#x2F;src&#x2F;litegraph.js:4090</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>unbinds mouse events from the canvas</p>\n\n    </div>\n\n\n\n\n</div>\n            </div>\n\n\n\n    </div>\n</div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n<script src=\"../assets/vendor/prettify/prettify-min.js\"></script>\n<script>prettyPrint();</script>\n<script src=\"../assets/js/yui-prettify.js\"></script>\n<script src=\"../assets/../api.js\"></script>\n<script src=\"../assets/js/api-filter.js\"></script>\n<script src=\"../assets/js/api-list.js\"></script>\n<script src=\"../assets/js/api-search.js\"></script>\n<script src=\"../assets/js/apidocs.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "doc/classes/LGraphNode.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>LGraphNode</title>\n    <link rel=\"stylesheet\" href=\"http://yui.yahooapis.com/3.9.1/build/cssgrids/cssgrids-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/vendor/prettify/prettify-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/css/main.css\" id=\"site_styles\">\n    <link rel=\"icon\" href=\"../assets/favicon.ico\">\n    <script src=\"http://yui.yahooapis.com/combo?3.9.1/build/yui/yui-min.js\"></script>\n</head>\n<body class=\"yui3-skin-sam\">\n\n<div id=\"doc\">\n    <div id=\"hd\" class=\"yui3-g header\">\n        <div class=\"yui3-u-3-4\">\n                <h1><img src=\"../assets/css/logo.png\" title=\"\" width=\"117\" height=\"52\"></h1>\n        </div>\n        <div class=\"yui3-u-1-4 version\">\n            <em>API Docs for: </em>\n        </div>\n    </div>\n    <div id=\"bd\" class=\"yui3-g\">\n\n        <div class=\"yui3-u-1-4\">\n            <div id=\"docs-sidebar\" class=\"sidebar apidocs\">\n                <div id=\"api-list\">\n                    <h2 class=\"off-left\">APIs</h2>\n                    <div id=\"api-tabview\" class=\"tabview\">\n                        <ul class=\"tabs\">\n                            <li><a href=\"#api-classes\">Classes</a></li>\n                            <li><a href=\"#api-modules\">Modules</a></li>\n                        </ul>\n                \n                        <div id=\"api-tabview-filter\">\n                            <input type=\"search\" id=\"api-filter\" placeholder=\"Type to filter APIs\">\n                        </div>\n                \n                        <div id=\"api-tabview-panel\">\n                            <ul id=\"api-classes\" class=\"apis classes\">\n                                <li><a href=\"../classes/ContextMenu.html\">ContextMenu</a></li>\n                                <li><a href=\"../classes/LGraph.html\">LGraph</a></li>\n                                <li><a href=\"../classes/LGraphCanvas.html\">LGraphCanvas</a></li>\n                                <li><a href=\"../classes/LGraphNode.html\">LGraphNode</a></li>\n                                <li><a href=\"../classes/LiteGraph.html\">LiteGraph</a></li>\n                            </ul>\n                \n                \n                            <ul id=\"api-modules\" class=\"apis modules\">\n                            </ul>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <div class=\"yui3-u-3-4\">\n                <div id=\"api-options\">\n                    Show:\n                    <label for=\"api-show-inherited\">\n                        <input type=\"checkbox\" id=\"api-show-inherited\" checked>\n                        Inherited\n                    </label>\n            \n                    <label for=\"api-show-protected\">\n                        <input type=\"checkbox\" id=\"api-show-protected\">\n                        Protected\n                    </label>\n            \n                    <label for=\"api-show-private\">\n                        <input type=\"checkbox\" id=\"api-show-private\">\n                        Private\n                    </label>\n                    <label for=\"api-show-deprecated\">\n                        <input type=\"checkbox\" id=\"api-show-deprecated\">\n                        Deprecated\n                    </label>\n            \n                </div>\n            \n            <div class=\"apidocs\">\n                <div id=\"docs-main\">\n                    <div class=\"content\">\n<h1>LGraphNode Class</h1>\n<div class=\"box meta\">\n\n\n        <div class=\"foundat\">\n            Defined in: <a href=\"../files/.._src_litegraph.js.html#l1830\"><code>..&#x2F;src&#x2F;litegraph.js:1830</code></a>\n        </div>\n\n\n</div>\n\n\n<div class=\"box intro\">\n    <p>Base Class for all the node type classes</p>\n\n</div>\n\n\n<div id=\"classdocs\" class=\"tabview\">\n    <ul class=\"api-class-tabs\">\n        <li class=\"api-class-tab index\"><a href=\"#index\">Index</a></li>\n\n            <li class=\"api-class-tab methods\"><a href=\"#methods\">Methods</a></li>\n    </ul>\n\n    <div>\n        <div id=\"index\" class=\"api-class-tabpanel index\">\n            <h2 class=\"off-left\">Item Index</h2>\n\n                <div class=\"index-section methods\">\n                    <h3>Methods</h3>\n\n                    <ul class=\"index-list methods\">\n                            <li class=\"index-item method\">\n                                <a href=\"#method_addConnection\">addConnection</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_addInput\">addInput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_addInputs\">addInputs</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_addOutput\">addOutput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_addOutputs\">addOutputs</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_addProperty\">addProperty</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_addWidget\">addWidget</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_clearTriggeredSlot\">clearTriggeredSlot</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_collapse\">collapse</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_computeSize\">computeSize</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_configure\">configure</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_connect\">connect</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_disconnectInput\">disconnectInput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_disconnectOutput\">disconnectOutput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_findInputSlot\">findInputSlot</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_findOutputSlot\">findOutputSlot</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getBounding\">getBounding</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getConnectionPos\">getConnectionPos</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getInputData\">getInputData</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getInputDataByName\">getInputDataByName</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getInputDataType\">getInputDataType</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getInputInfo\">getInputInfo</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getInputNode\">getInputNode</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getInputOrProperty\">getInputOrProperty</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getOutputData\">getOutputData</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getOutputInfo\">getOutputInfo</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getOutputNodes\">getOutputNodes</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getSlotInPosition\">getSlotInPosition</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getTitle\">getTitle</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_isAnyOutputConnected\">isAnyOutputConnected</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_isInputConnected\">isInputConnected</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_isOutputConnected\">isOutputConnected</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_isPointInside\">isPointInside</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_pin\">pin</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_removeInput\">removeInput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_removeOutput\">removeOutput</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_serialize\">serialize</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_setOutputData\">setOutputData</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_setOutputDataType\">setOutputDataType</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_toString\">toString</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_trigger\">trigger</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_triggerSlot\">triggerSlot</a>\n\n                            </li>\n                    </ul>\n                </div>\n\n\n\n        </div>\n\n            <div id=\"methods\" class=\"api-class-tabpanel\">\n                <h2 class=\"off-left\">Methods</h2>\n\n<div id=\"method_addConnection\" class=\"method item\">\n    <h3 class=\"name\"><code>addConnection</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n                <li class=\"arg\">\n                        <code>pos</code>\n                </li>\n                <li class=\"arg\">\n                        <code>direction</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2665\"><code>..&#x2F;src&#x2F;litegraph.js:2665</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>add an special connection to this node (used for special kinds of graphs)</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>string defining the input type (&quot;vec3&quot;,&quot;number&quot;,...)</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">pos</code>\n                        <span class=\"type\">x,y</span>\n\n\n                    <div class=\"param-description\">\n                        <p>position of the connection inside the node</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">direction</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>if is input or output</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_addInput\" class=\"method item\">\n    <h3 class=\"name\"><code>addInput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n                <li class=\"arg\">\n                        <code>extra_info</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2590\"><code>..&#x2F;src&#x2F;litegraph.js:2590</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>add a new input slot to use in this node</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>string defining the input type (&quot;vec3&quot;,&quot;number&quot;,...), it its a generic one use 0</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">extra_info</code>\n                        <span class=\"type\">Object</span>\n\n\n                    <div class=\"param-description\">\n                        <p>this can be used to have special properties of an input (label, color, position, etc)</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_addInputs\" class=\"method item\">\n    <h3 class=\"name\"><code>addInputs</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>array</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2615\"><code>..&#x2F;src&#x2F;litegraph.js:2615</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>add several new input slots in this node</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">array</code>\n                        <span class=\"type\">Array</span>\n\n\n                    <div class=\"param-description\">\n                        <p>of triplets like [[name,type,extra_info],[...]]</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_addOutput\" class=\"method item\">\n    <h3 class=\"name\"><code>addOutput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n                <li class=\"arg\">\n                        <code>extra_info</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2511\"><code>..&#x2F;src&#x2F;litegraph.js:2511</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>add a new output slot to use in this node</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>string defining the output type (&quot;vec3&quot;,&quot;number&quot;,...)</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">extra_info</code>\n                        <span class=\"type\">Object</span>\n\n\n                    <div class=\"param-description\">\n                        <p>this can be used to have special properties of an output (label, special color, position, etc)</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_addOutputs\" class=\"method item\">\n    <h3 class=\"name\"><code>addOutputs</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>array</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2535\"><code>..&#x2F;src&#x2F;litegraph.js:2535</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>add a new output slot to use in this node</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">array</code>\n                        <span class=\"type\">Array</span>\n\n\n                    <div class=\"param-description\">\n                        <p>of triplets like [[name,type,extra_info],[...]]</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_addProperty\" class=\"method item\">\n    <h3 class=\"name\"><code>addProperty</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>default_value</code>\n                </li>\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n                <li class=\"arg\">\n                        <code>extra_info</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2485\"><code>..&#x2F;src&#x2F;litegraph.js:2485</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>add a new property to this node</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">default_value</code>\n                        <span class=\"type\"></span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>string defining the output type (&quot;vec3&quot;,&quot;number&quot;,...)</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">extra_info</code>\n                        <span class=\"type\">Object</span>\n\n\n                    <div class=\"param-description\">\n                        <p>this can be used to have special properties of the property (like values, etc)</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_addWidget\" class=\"method item\">\n    <h3 class=\"name\"><code>addWidget</code></h3>\n\n        <span class=\"paren\">()</span>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Object</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2753\"><code>..&#x2F;src&#x2F;litegraph.js:2753</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Allows to pass</p>\n\n    </div>\n\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Object</span>:\n                    <p>the created widget</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_clearTriggeredSlot\" class=\"method item\">\n    <h3 class=\"name\"><code>clearTriggeredSlot</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n                <li class=\"arg\">\n                        <code>link_id</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2453\"><code>..&#x2F;src&#x2F;litegraph.js:2453</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>clears the trigger slot animation</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the index of the output slot</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">link_id</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>[optional] in case you want to trigger and specific output link in a slot</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_collapse\" class=\"method item\">\n    <h3 class=\"name\"><code>collapse</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l3390\"><code>..&#x2F;src&#x2F;litegraph.js:3390</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Collapse the node to make it smaller on the canvas</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_computeSize\" class=\"method item\">\n    <h3 class=\"name\"><code>computeSize</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>minHeight</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Number</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2686\"><code>..&#x2F;src&#x2F;litegraph.js:2686</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>computes the size of a node according to its inputs and output slots</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">minHeight</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Number</span>:\n                    <p>the total size</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_configure\" class=\"method item\">\n    <h3 class=\"name\"><code>configure</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1881\"><code>..&#x2F;src&#x2F;litegraph.js:1881</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>configure a node from an object containing the serialized info</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_connect\" class=\"method item\">\n    <h3 class=\"name\"><code>connect</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n                <li class=\"arg\">\n                        <code>node</code>\n                </li>\n                <li class=\"arg\">\n                        <code>target_slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Object</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2899\"><code>..&#x2F;src&#x2F;litegraph.js:2899</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>connect this node output to the input of another node</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number_or_string</span>\n\n\n                    <div class=\"param-description\">\n                        <p>(could be the number of the slot or the string with the name of the slot)</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">node</code>\n                        <span class=\"type\"><a href=\"../classes/LGraphNode.html\" class=\"crosslink\">LGraphNode</a></span>\n\n\n                    <div class=\"param-description\">\n                        <p>the target node</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">target_slot</code>\n                        <span class=\"type\">Number_or_string</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Object</span>:\n                    <p>the link_info is created, otherwise null</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_disconnectInput\" class=\"method item\">\n    <h3 class=\"name\"><code>disconnectInput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Boolean</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l3132\"><code>..&#x2F;src&#x2F;litegraph.js:3132</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>disconnect one input</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number_or_string</span>\n\n\n                    <div class=\"param-description\">\n                        <p>(could be the number of the slot or the string with the name of the slot)</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Boolean</span>:\n                    <p>if it was disconnected succesfully</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_disconnectOutput\" class=\"method item\">\n    <h3 class=\"name\"><code>disconnectOutput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n                <li class=\"arg\">\n                        <code>target_node</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Boolean</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l3024\"><code>..&#x2F;src&#x2F;litegraph.js:3024</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>disconnect one output to an specific node</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number_or_string</span>\n\n\n                    <div class=\"param-description\">\n                        <p>(could be the number of the slot or the string with the name of the slot)</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">target_node</code>\n                        <span class=\"type\"><a href=\"../classes/LGraphNode.html\" class=\"crosslink\">LGraphNode</a></span>\n\n\n                    <div class=\"param-description\">\n                        <p>the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Boolean</span>:\n                    <p>if it was disconnected succesfully</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_findInputSlot\" class=\"method item\">\n    <h3 class=\"name\"><code>findInputSlot</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Number</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2868\"><code>..&#x2F;src&#x2F;litegraph.js:2868</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>returns the input slot with a given name (used for dynamic slots), -1 if not found</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the name of the slot</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Number</span>:\n                    <p>the slot (-1 if not found)</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_findOutputSlot\" class=\"method item\">\n    <h3 class=\"name\"><code>findOutputSlot</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Number</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2884\"><code>..&#x2F;src&#x2F;litegraph.js:2884</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>returns the output slot with a given name (used for dynamic slots), -1 if not found</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the name of the slot</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Number</span>:\n                    <p>the slot (-1 if not found)</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getBounding\" class=\"method item\">\n    <h3 class=\"name\"><code>getBounding</code></h3>\n\n        <span class=\"paren\">()</span>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Float32Array4</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2791\"><code>..&#x2F;src&#x2F;litegraph.js:2791</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>returns the bounding of the object, used for rendering purposes\nbounding is: [topleft_cornerx, topleft_cornery, width, height]</p>\n\n    </div>\n\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Float32Array4</span>:\n                    <p>the total size</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getConnectionPos\" class=\"method item\">\n    <h3 class=\"name\"><code>getConnectionPos</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>is_input</code>\n                </li>\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n                <li class=\"arg\">\n                        <code>out</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">x,y</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l3206\"><code>..&#x2F;src&#x2F;litegraph.js:3206</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>returns the center of a connection point in canvas coords</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">is_input</code>\n                        <span class=\"type\">Boolean</span>\n\n\n                    <div class=\"param-description\">\n                        <p>true if if a input slot, false if it is an output</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number_or_string</span>\n\n\n                    <div class=\"param-description\">\n                        <p>(could be the number of the slot or the string with the name of the slot)</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">out</code>\n                        <span class=\"type\">Vec2</span>\n\n\n                    <div class=\"param-description\">\n                        <p>[optional] a place to store the output, to free garbage</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">x,y</span>:\n                    <p>the position</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getInputData\" class=\"method item\">\n    <h3 class=\"name\"><code>getInputData</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n                <li class=\"arg\">\n                        <code>force_update</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\"></span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2129\"><code>..&#x2F;src&#x2F;litegraph.js:2129</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Retrieves the input data (data traveling through the connection) from one slot</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">force_update</code>\n                        <span class=\"type\">Boolean</span>\n\n\n                    <div class=\"param-description\">\n                        <p>if set to true it will force the connected node of this slot to output data into this link</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\"></span>:\n                    <p>data or if it is not connected returns undefined</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getInputDataByName\" class=\"method item\">\n    <h3 class=\"name\"><code>getInputDataByName</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot_name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>force_update</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\"></span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2191\"><code>..&#x2F;src&#x2F;litegraph.js:2191</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Retrieves the input data from one slot using its name instead of slot number</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot_name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">force_update</code>\n                        <span class=\"type\">Boolean</span>\n\n\n                    <div class=\"param-description\">\n                        <p>if set to true it will force the connected node of this slot to output data into this link</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\"></span>:\n                    <p>data or if it is not connected returns null</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getInputDataType\" class=\"method item\">\n    <h3 class=\"name\"><code>getInputDataType</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">String</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2165\"><code>..&#x2F;src&#x2F;litegraph.js:2165</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Retrieves the input data type (in case this supports multiple input types)</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">String</span>:\n                    <p>datatype in string format</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getInputInfo\" class=\"method item\">\n    <h3 class=\"name\"><code>getInputInfo</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Object</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2220\"><code>..&#x2F;src&#x2F;litegraph.js:2220</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>tells you info about an input connection (which node, type, etc)</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Object</span>:\n                    <p>object or null { link: id, name: string, type: string or 0 }</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getInputNode\" class=\"method item\">\n    <h3 class=\"name\"><code>getInputNode</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\"><a href=\"../classes/LGraphNode.html\" class=\"crosslink\">LGraphNode</a></span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2235\"><code>..&#x2F;src&#x2F;litegraph.js:2235</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>returns the node connected in the input slot</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\"><a href=\"../classes/LGraphNode.html\" class=\"crosslink\">LGraphNode</a></span>:\n                    <p>node or null</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getInputOrProperty\" class=\"method item\">\n    <h3 class=\"name\"><code>getInputOrProperty</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\"></span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2257\"><code>..&#x2F;src&#x2F;litegraph.js:2257</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>returns the value of an input with this name, otherwise checks if there is a property with that name</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\"></span>:\n                    <p>value</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getOutputData\" class=\"method item\">\n    <h3 class=\"name\"><code>getOutputData</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Object</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2284\"><code>..&#x2F;src&#x2F;litegraph.js:2284</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>tells you the last output data that went in that slot</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Object</span>:\n                    <p>object or null</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getOutputInfo\" class=\"method item\">\n    <h3 class=\"name\"><code>getOutputInfo</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Object</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2302\"><code>..&#x2F;src&#x2F;litegraph.js:2302</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>tells you info about an output connection (which node, type, etc)</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Object</span>:\n                    <p>object or null { name: string, type: string, links: [ ids of links in number ] }</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getOutputNodes\" class=\"method item\">\n    <h3 class=\"name\"><code>getOutputNodes</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Array</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2347\"><code>..&#x2F;src&#x2F;litegraph.js:2347</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>retrieves all the nodes connected to this output slot</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Array</span>:\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getSlotInPosition\" class=\"method item\">\n    <h3 class=\"name\"><code>getSlotInPosition</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>x</code>\n                </li>\n                <li class=\"arg\">\n                        <code>y</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Object</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2836\"><code>..&#x2F;src&#x2F;litegraph.js:2836</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>checks if a point is inside a node slot, and returns info about which slot</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">x</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">y</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Object</span>:\n                    <p>if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getTitle\" class=\"method item\">\n    <h3 class=\"name\"><code>getTitle</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2051\"><code>..&#x2F;src&#x2F;litegraph.js:2051</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>get the title string</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_isAnyOutputConnected\" class=\"method item\">\n    <h3 class=\"name\"><code>isAnyOutputConnected</code></h3>\n\n        <span class=\"paren\">()</span>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Boolean</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2331\"><code>..&#x2F;src&#x2F;litegraph.js:2331</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>tells you if there is any connection in the output slots</p>\n\n    </div>\n\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Boolean</span>:\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_isInputConnected\" class=\"method item\">\n    <h3 class=\"name\"><code>isInputConnected</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Boolean</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2207\"><code>..&#x2F;src&#x2F;litegraph.js:2207</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>tells you if there is a connection in one input slot</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Boolean</span>:\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_isOutputConnected\" class=\"method item\">\n    <h3 class=\"name\"><code>isOutputConnected</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Boolean</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2318\"><code>..&#x2F;src&#x2F;litegraph.js:2318</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>tells you if there is a connection in one output slot</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Boolean</span>:\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_isPointInside\" class=\"method item\">\n    <h3 class=\"name\"><code>isPointInside</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>x</code>\n                </li>\n                <li class=\"arg\">\n                        <code>y</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Boolean</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2810\"><code>..&#x2F;src&#x2F;litegraph.js:2810</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>checks if a point is inside the shape of a node</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">x</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">y</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Boolean</span>:\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_pin\" class=\"method item\">\n    <h3 class=\"name\"><code>pin</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l3406\"><code>..&#x2F;src&#x2F;litegraph.js:3406</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Forces the node to do not move or realign on Z</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_removeInput\" class=\"method item\">\n    <h3 class=\"name\"><code>removeInput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2641\"><code>..&#x2F;src&#x2F;litegraph.js:2641</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>remove an existing input slot</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_removeOutput\" class=\"method item\">\n    <h3 class=\"name\"><code>removeOutput</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2561\"><code>..&#x2F;src&#x2F;litegraph.js:2561</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>remove an existing output slot</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_serialize\" class=\"method item\">\n    <h3 class=\"name\"><code>serialize</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l1949\"><code>..&#x2F;src&#x2F;litegraph.js:1949</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>serialize the content</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_setOutputData\" class=\"method item\">\n    <h3 class=\"name\"><code>setOutputData</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n                <li class=\"arg\">\n                        <code>data</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2064\"><code>..&#x2F;src&#x2F;litegraph.js:2064</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>sets the output data</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">data</code>\n                        <span class=\"type\"></span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_setOutputDataType\" class=\"method item\">\n    <h3 class=\"name\"><code>setOutputDataType</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n                <li class=\"arg\">\n                        <code>datatype</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2100\"><code>..&#x2F;src&#x2F;litegraph.js:2100</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>sets the output data type, useful when you want to be able to overwrite the data type</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">datatype</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_toString\" class=\"method item\">\n    <h3 class=\"name\"><code>toString</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2039\"><code>..&#x2F;src&#x2F;litegraph.js:2039</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>serialize and stringify</p>\n\n    </div>\n\n\n\n\n</div>\n<div id=\"method_trigger\" class=\"method item\">\n    <h3 class=\"name\"><code>trigger</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>event</code>\n                </li>\n                <li class=\"arg\">\n                        <code>param</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2380\"><code>..&#x2F;src&#x2F;litegraph.js:2380</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Triggers an event in this node, this will trigger any output with the same name</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">event</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>name ( &quot;on_play&quot;, ... ) if action is equivalent to false then the event is send to all</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">param</code>\n                        <span class=\"type\"></span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_triggerSlot\" class=\"method item\">\n    <h3 class=\"name\"><code>triggerSlot</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>slot</code>\n                </li>\n                <li class=\"arg\">\n                        <code>param</code>\n                </li>\n                <li class=\"arg\">\n                        <code>link_id</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l2403\"><code>..&#x2F;src&#x2F;litegraph.js:2403</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Triggers an slot event in this node</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">slot</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>the index of the output slot</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">param</code>\n                        <span class=\"type\"></span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">link_id</code>\n                        <span class=\"type\">Number</span>\n\n\n                    <div class=\"param-description\">\n                        <p>[optional] in case you want to trigger and specific output link in a slot</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n            </div>\n\n\n\n    </div>\n</div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n<script src=\"../assets/vendor/prettify/prettify-min.js\"></script>\n<script>prettyPrint();</script>\n<script src=\"../assets/js/yui-prettify.js\"></script>\n<script src=\"../assets/../api.js\"></script>\n<script src=\"../assets/js/api-filter.js\"></script>\n<script src=\"../assets/js/api-list.js\"></script>\n<script src=\"../assets/js/api-search.js\"></script>\n<script src=\"../assets/js/apidocs.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "doc/classes/LiteGraph.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>LiteGraph</title>\n    <link rel=\"stylesheet\" href=\"http://yui.yahooapis.com/3.9.1/build/cssgrids/cssgrids-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/vendor/prettify/prettify-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/css/main.css\" id=\"site_styles\">\n    <link rel=\"icon\" href=\"../assets/favicon.ico\">\n    <script src=\"http://yui.yahooapis.com/combo?3.9.1/build/yui/yui-min.js\"></script>\n</head>\n<body class=\"yui3-skin-sam\">\n\n<div id=\"doc\">\n    <div id=\"hd\" class=\"yui3-g header\">\n        <div class=\"yui3-u-3-4\">\n                <h1><img src=\"../assets/css/logo.png\" title=\"\" width=\"117\" height=\"52\"></h1>\n        </div>\n        <div class=\"yui3-u-1-4 version\">\n            <em>API Docs for: </em>\n        </div>\n    </div>\n    <div id=\"bd\" class=\"yui3-g\">\n\n        <div class=\"yui3-u-1-4\">\n            <div id=\"docs-sidebar\" class=\"sidebar apidocs\">\n                <div id=\"api-list\">\n                    <h2 class=\"off-left\">APIs</h2>\n                    <div id=\"api-tabview\" class=\"tabview\">\n                        <ul class=\"tabs\">\n                            <li><a href=\"#api-classes\">Classes</a></li>\n                            <li><a href=\"#api-modules\">Modules</a></li>\n                        </ul>\n                \n                        <div id=\"api-tabview-filter\">\n                            <input type=\"search\" id=\"api-filter\" placeholder=\"Type to filter APIs\">\n                        </div>\n                \n                        <div id=\"api-tabview-panel\">\n                            <ul id=\"api-classes\" class=\"apis classes\">\n                                <li><a href=\"../classes/ContextMenu.html\">ContextMenu</a></li>\n                                <li><a href=\"../classes/LGraph.html\">LGraph</a></li>\n                                <li><a href=\"../classes/LGraphCanvas.html\">LGraphCanvas</a></li>\n                                <li><a href=\"../classes/LGraphNode.html\">LGraphNode</a></li>\n                                <li><a href=\"../classes/LiteGraph.html\">LiteGraph</a></li>\n                            </ul>\n                \n                \n                            <ul id=\"api-modules\" class=\"apis modules\">\n                            </ul>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <div class=\"yui3-u-3-4\">\n                <div id=\"api-options\">\n                    Show:\n                    <label for=\"api-show-inherited\">\n                        <input type=\"checkbox\" id=\"api-show-inherited\" checked>\n                        Inherited\n                    </label>\n            \n                    <label for=\"api-show-protected\">\n                        <input type=\"checkbox\" id=\"api-show-protected\">\n                        Protected\n                    </label>\n            \n                    <label for=\"api-show-private\">\n                        <input type=\"checkbox\" id=\"api-show-private\">\n                        Private\n                    </label>\n                    <label for=\"api-show-deprecated\">\n                        <input type=\"checkbox\" id=\"api-show-deprecated\">\n                        Deprecated\n                    </label>\n            \n                </div>\n            \n            <div class=\"apidocs\">\n                <div id=\"docs-main\">\n                    <div class=\"content\">\n<h1>LiteGraph Class</h1>\n<div class=\"box meta\">\n\n\n        <div class=\"foundat\">\n            Defined in: <a href=\"../files/.._src_litegraph.js.html#l6\"><code>..&#x2F;src&#x2F;litegraph.js:6</code></a>\n        </div>\n\n\n</div>\n\n\n<div class=\"box intro\">\n    <p>The Global Scope. It contains all the registered node classes.</p>\n\n</div>\n\n    <div class=\"constructor\">\n        <h2>Constructor</h2>\n<div id=\"method_LiteGraph\" class=\"method item\">\n    <h3 class=\"name\"><code>LiteGraph</code></h3>\n\n        <span class=\"paren\">()</span>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l6\"><code>..&#x2F;src&#x2F;litegraph.js:6</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        \n    </div>\n\n\n\n\n</div>\n    </div>\n\n<div id=\"classdocs\" class=\"tabview\">\n    <ul class=\"api-class-tabs\">\n        <li class=\"api-class-tab index\"><a href=\"#index\">Index</a></li>\n\n            <li class=\"api-class-tab methods\"><a href=\"#methods\">Methods</a></li>\n    </ul>\n\n    <div>\n        <div id=\"index\" class=\"api-class-tabpanel index\">\n            <h2 class=\"off-left\">Item Index</h2>\n\n                <div class=\"index-section methods\">\n                    <h3>Methods</h3>\n\n                    <ul class=\"index-list methods\">\n                            <li class=\"index-item method\">\n                                <a href=\"#method_addNodeMethod\">addNodeMethod</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_createNode\">createNode</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getNodeType\">getNodeType</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getNodeType\">getNodeType</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_getNodeTypesCategories\">getNodeTypesCategories</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_registerNodeType\">registerNodeType</a>\n\n                            </li>\n                            <li class=\"index-item method\">\n                                <a href=\"#method_wrapFunctionAsNode\">wrapFunctionAsNode</a>\n\n                            </li>\n                    </ul>\n                </div>\n\n\n\n        </div>\n\n            <div id=\"methods\" class=\"api-class-tabpanel\">\n                <h2 class=\"off-left\">Methods</h2>\n\n<div id=\"method_addNodeMethod\" class=\"method item\">\n    <h3 class=\"name\"><code>addNodeMethod</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>func</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l193\"><code>..&#x2F;src&#x2F;litegraph.js:193</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Adds this method to all nodetypes, existing and to be created\n(You can add it to LGraphNode.prototype but then existing node types wont have it)</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">func</code>\n                        <span class=\"type\">Function</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_createNode\" class=\"method item\">\n    <h3 class=\"name\"><code>createNode</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>options</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l211\"><code>..&#x2F;src&#x2F;litegraph.js:211</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Create a node of a given type with a name. The node is not attached to any graph yet.</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>full name of the node class. p.e. &quot;math/sin&quot;</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>a name to distinguish from other nodes</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">options</code>\n                        <span class=\"type\">Object</span>\n\n\n                    <div class=\"param-description\">\n                        <p>to set options</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_getNodeType\" class=\"method item\">\n    <h3 class=\"name\"><code>getNodeType</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Class</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l270\"><code>..&#x2F;src&#x2F;litegraph.js:270</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns a registered node type with a given name</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>full name of the node class. p.e. &quot;math/sin&quot;</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Class</span>:\n                    <p>the node class</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getNodeType\" class=\"method item\">\n    <h3 class=\"name\"><code>getNodeType</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>category</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Array</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l281\"><code>..&#x2F;src&#x2F;litegraph.js:281</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns a list of node types matching one category</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">category</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>category name</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Array</span>:\n                    <p>array with all the node classes</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_getNodeTypesCategories\" class=\"method item\">\n    <h3 class=\"name\"><code>getNodeTypesCategories</code></h3>\n\n        <span class=\"paren\">()</span>\n\n        <span class=\"returns-inline\">\n            <span class=\"type\">Array</span>\n        </span>\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l309\"><code>..&#x2F;src&#x2F;litegraph.js:309</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Returns a list with all the node type categories</p>\n\n    </div>\n\n\n        <div class=\"returns\">\n            <h4>Returns:</h4>\n\n            <div class=\"returns-description\">\n                        <span class=\"type\">Array</span>:\n                    <p>array with all the names of the categories</p>\n\n            </div>\n        </div>\n\n\n</div>\n<div id=\"method_registerNodeType\" class=\"method item\">\n    <h3 class=\"name\"><code>registerNodeType</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>type</code>\n                </li>\n                <li class=\"arg\">\n                        <code>base_class</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l93\"><code>..&#x2F;src&#x2F;litegraph.js:93</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Register a node class so it can be listed when the user wants to create a new one</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>name of the node and path</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">base_class</code>\n                        <span class=\"type\">Class</span>\n\n\n                    <div class=\"param-description\">\n                        <p>class containing the structure of a node</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n<div id=\"method_wrapFunctionAsNode\" class=\"method item\">\n    <h3 class=\"name\"><code>wrapFunctionAsNode</code></h3>\n\n        <div class=\"args\">\n            <span class=\"paren\">(</span><ul class=\"args-list inline commas\">\n                <li class=\"arg\">\n                        <code>name</code>\n                </li>\n                <li class=\"arg\">\n                        <code>func</code>\n                </li>\n                <li class=\"arg\">\n                        <code>param_types</code>\n                </li>\n                <li class=\"arg\">\n                        <code>return_type</code>\n                </li>\n                <li class=\"arg\">\n                        <code>properties</code>\n                </li>\n            </ul><span class=\"paren\">)</span>\n        </div>\n\n\n\n\n\n\n\n\n    <div class=\"meta\">\n                <p>\n                Defined in\n        <a href=\"../files/.._src_litegraph.js.html#l160\"><code>..&#x2F;src&#x2F;litegraph.js:160</code></a>\n        </p>\n\n\n\n    </div>\n\n    <div class=\"description\">\n        <p>Create a new node type by passing a function, it wraps it with a propper class and generates inputs according to the parameters of the function.\nUseful to wrap simple methods that do not require properties, and that only process some input to generate an output.</p>\n\n    </div>\n\n        <div class=\"params\">\n            <h4>Parameters:</h4>\n\n            <ul class=\"params-list\">\n                <li class=\"param\">\n                        <code class=\"param-name\">name</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>node name with namespace (p.e.: 'math/sum')</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">func</code>\n                        <span class=\"type\">Function</span>\n\n\n                    <div class=\"param-description\">\n                         \n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">param_types</code>\n                        <span class=\"type\">Array</span>\n\n\n                    <div class=\"param-description\">\n                        <p>[optional] an array containing the type of every parameter, otherwise parameters will accept any type</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">return_type</code>\n                        <span class=\"type\">String</span>\n\n\n                    <div class=\"param-description\">\n                        <p>[optional] string with the return type, otherwise it will be generic</p>\n\n                    </div>\n\n                </li>\n                <li class=\"param\">\n                        <code class=\"param-name\">properties</code>\n                        <span class=\"type\">Object</span>\n\n\n                    <div class=\"param-description\">\n                        <p>[optional] properties to be configurable</p>\n\n                    </div>\n\n                </li>\n            </ul>\n        </div>\n\n\n\n</div>\n            </div>\n\n\n\n    </div>\n</div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n<script src=\"../assets/vendor/prettify/prettify-min.js\"></script>\n<script>prettyPrint();</script>\n<script src=\"../assets/js/yui-prettify.js\"></script>\n<script src=\"../assets/../api.js\"></script>\n<script src=\"../assets/js/api-filter.js\"></script>\n<script src=\"../assets/js/api-list.js\"></script>\n<script src=\"../assets/js/api-search.js\"></script>\n<script src=\"../assets/js/apidocs.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "doc/classes/index.html",
    "content": "<!doctype html>\n<html>\n    <head>\n        <title>Redirector</title>\n        <meta http-equiv=\"refresh\" content=\"0;url=../\">\n    </head>\n    <body>\n        <a href=\"../\">Click here to redirect</a>\n    </body>\n</html>\n"
  },
  {
    "path": "doc/data.json",
    "content": "{\n    \"project\": {},\n    \"files\": {\n        \"../src/litegraph.js\": {\n            \"name\": \"../src/litegraph.js\",\n            \"modules\": {},\n            \"classes\": {\n                \"LiteGraph\": 1,\n                \"LGraph\": 1,\n                \"LGraphNode\": 1,\n                \"LGraphCanvas\": 1,\n                \"ContextMenu\": 1\n            },\n            \"fors\": {},\n            \"namespaces\": {}\n        }\n    },\n    \"modules\": {},\n    \"classes\": {\n        \"LiteGraph\": {\n            \"name\": \"LiteGraph\",\n            \"shortname\": \"LiteGraph\",\n            \"classitems\": [],\n            \"plugins\": [],\n            \"extensions\": [],\n            \"plugin_for\": [],\n            \"extension_for\": [],\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 6,\n            \"description\": \"The Global Scope. It contains all the registered node classes.\",\n            \"is_constructor\": 1\n        },\n        \"LGraph\": {\n            \"name\": \"LGraph\",\n            \"shortname\": \"LGraph\",\n            \"classitems\": [],\n            \"plugins\": [],\n            \"extensions\": [],\n            \"plugin_for\": [],\n            \"extension_for\": [],\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 438,\n            \"description\": \"LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.\",\n            \"is_constructor\": 1,\n            \"params\": [\n                {\n                    \"name\": \"o\",\n                    \"description\": \"data from previous serialization [optional]\",\n                    \"type\": \"Object\"\n                }\n            ]\n        },\n        \"LGraphNode\": {\n            \"name\": \"LGraphNode\",\n            \"shortname\": \"LGraphNode\",\n            \"classitems\": [],\n            \"plugins\": [],\n            \"extensions\": [],\n            \"plugin_for\": [],\n            \"extension_for\": [],\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1830,\n            \"description\": \"Base Class for all the node type classes\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"a name for the node\",\n                    \"type\": \"String\"\n                }\n            ]\n        },\n        \"LGraphCanvas\": {\n            \"name\": \"LGraphCanvas\",\n            \"shortname\": \"LGraphCanvas\",\n            \"classitems\": [],\n            \"plugins\": [],\n            \"extensions\": [],\n            \"plugin_for\": [],\n            \"extension_for\": [],\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4164,\n            \"description\": \"marks as dirty the canvas, this way it will be rendered again\",\n            \"is_constructor\": 1,\n            \"params\": [\n                {\n                    \"name\": \"canvas\",\n                    \"description\": \"the canvas where you want to render (it accepts a selector in string format or the canvas element itself)\",\n                    \"type\": \"HTMLCanvas\"\n                },\n                {\n                    \"name\": \"graph\",\n                    \"description\": \"[optional]\",\n                    \"type\": \"LGraph\"\n                },\n                {\n                    \"name\": \"options\",\n                    \"description\": \"[optional] { skip_rendering, autoresize }\",\n                    \"type\": \"Object\"\n                }\n            ],\n            \"itemtype\": \"method\"\n        },\n        \"ContextMenu\": {\n            \"name\": \"ContextMenu\",\n            \"shortname\": \"ContextMenu\",\n            \"classitems\": [],\n            \"plugins\": [],\n            \"extensions\": [],\n            \"plugin_for\": [],\n            \"extension_for\": [],\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 8395,\n            \"description\": \"ContextMenu from LiteGUI\",\n            \"is_constructor\": 1,\n            \"params\": [\n                {\n                    \"name\": \"values\",\n                    \"description\": \"(allows object { title: \\\"Nice text\\\", callback: function ... })\",\n                    \"type\": \"Array\"\n                },\n                {\n                    \"name\": \"options\",\n                    \"description\": \"[optional] Some options:\\\\\\n- title: title to show on top of the menu\\n- callback: function to call when an option is clicked, it receives the item information\\n- ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback\\n- event: you can pass a MouseEvent, this way the ContextMenu appears in that position\",\n                    \"type\": \"Object\"\n                }\n            ]\n        }\n    },\n    \"elements\": {},\n    \"classitems\": [\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 93,\n            \"description\": \"Register a node class so it can be listed when the user wants to create a new one\",\n            \"itemtype\": \"method\",\n            \"name\": \"registerNodeType\",\n            \"params\": [\n                {\n                    \"name\": \"type\",\n                    \"description\": \"name of the node and path\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"base_class\",\n                    \"description\": \"class containing the structure of a node\",\n                    \"type\": \"Class\"\n                }\n            ],\n            \"class\": \"LiteGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 160,\n            \"description\": \"Create a new node type by passing a function, it wraps it with a propper class and generates inputs according to the parameters of the function.\\nUseful to wrap simple methods that do not require properties, and that only process some input to generate an output.\",\n            \"itemtype\": \"method\",\n            \"name\": \"wrapFunctionAsNode\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"node name with namespace (p.e.: 'math/sum')\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"func\",\n                    \"description\": \"\",\n                    \"type\": \"Function\"\n                },\n                {\n                    \"name\": \"param_types\",\n                    \"description\": \"[optional] an array containing the type of every parameter, otherwise parameters will accept any type\",\n                    \"type\": \"Array\"\n                },\n                {\n                    \"name\": \"return_type\",\n                    \"description\": \"[optional] string with the return type, otherwise it will be generic\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"properties\",\n                    \"description\": \"[optional] properties to be configurable\",\n                    \"type\": \"Object\"\n                }\n            ],\n            \"class\": \"LiteGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 193,\n            \"description\": \"Adds this method to all nodetypes, existing and to be created\\n(You can add it to LGraphNode.prototype but then existing node types wont have it)\",\n            \"itemtype\": \"method\",\n            \"name\": \"addNodeMethod\",\n            \"params\": [\n                {\n                    \"name\": \"func\",\n                    \"description\": \"\",\n                    \"type\": \"Function\"\n                }\n            ],\n            \"class\": \"LiteGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 211,\n            \"description\": \"Create a node of a given type with a name. The node is not attached to any graph yet.\",\n            \"itemtype\": \"method\",\n            \"name\": \"createNode\",\n            \"params\": [\n                {\n                    \"name\": \"type\",\n                    \"description\": \"full name of the node class. p.e. \\\"math/sin\\\"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"name\",\n                    \"description\": \"a name to distinguish from other nodes\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"options\",\n                    \"description\": \"to set options\",\n                    \"type\": \"Object\"\n                }\n            ],\n            \"class\": \"LiteGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 270,\n            \"description\": \"Returns a registered node type with a given name\",\n            \"itemtype\": \"method\",\n            \"name\": \"getNodeType\",\n            \"params\": [\n                {\n                    \"name\": \"type\",\n                    \"description\": \"full name of the node class. p.e. \\\"math/sin\\\"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"the node class\",\n                \"type\": \"Class\"\n            },\n            \"class\": \"LiteGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 281,\n            \"description\": \"Returns a list of node types matching one category\",\n            \"itemtype\": \"method\",\n            \"name\": \"getNodeType\",\n            \"params\": [\n                {\n                    \"name\": \"category\",\n                    \"description\": \"category name\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"array with all the node classes\",\n                \"type\": \"Array\"\n            },\n            \"class\": \"LiteGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 309,\n            \"description\": \"Returns a list with all the node type categories\",\n            \"itemtype\": \"method\",\n            \"name\": \"getNodeTypesCategories\",\n            \"return\": {\n                \"description\": \"array with all the names of the categories\",\n                \"type\": \"Array\"\n            },\n            \"class\": \"LiteGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 468,\n            \"description\": \"Removes all nodes from this graph\",\n            \"itemtype\": \"method\",\n            \"name\": \"clear\",\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 531,\n            \"description\": \"Attach Canvas to this graph\",\n            \"itemtype\": \"method\",\n            \"name\": \"attachCanvas\",\n            \"params\": [\n                {\n                    \"name\": \"graph_canvas\",\n                    \"description\": \"\",\n                    \"type\": \"GraphCanvas\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 550,\n            \"description\": \"Detach Canvas from this graph\",\n            \"itemtype\": \"method\",\n            \"name\": \"detachCanvas\",\n            \"params\": [\n                {\n                    \"name\": \"graph_canvas\",\n                    \"description\": \"\",\n                    \"type\": \"GraphCanvas\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 567,\n            \"description\": \"Starts running this graph every interval milliseconds.\",\n            \"itemtype\": \"method\",\n            \"name\": \"start\",\n            \"params\": [\n                {\n                    \"name\": \"interval\",\n                    \"description\": \"amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 609,\n            \"description\": \"Stops the execution loop of the graph\",\n            \"itemtype\": \"method\",\n            \"name\": \"stop execution\",\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 634,\n            \"description\": \"Run N steps (cycles) of the graph\",\n            \"itemtype\": \"method\",\n            \"name\": \"runStep\",\n            \"params\": [\n                {\n                    \"name\": \"num\",\n                    \"description\": \"number of steps to run, default is 1\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 716,\n            \"description\": \"Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than\\nnodes with only inputs.\",\n            \"itemtype\": \"method\",\n            \"name\": \"updateExecutionOrder\",\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 848,\n            \"description\": \"Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.\\nIt doesnt include the node itself\",\n            \"itemtype\": \"method\",\n            \"name\": \"getAncestors\",\n            \"return\": {\n                \"description\": \"an array with all the LGraphNodes that affect this node, in order of execution\",\n                \"type\": \"Array\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 885,\n            \"description\": \"Positions every node in a more readable manner\",\n            \"itemtype\": \"method\",\n            \"name\": \"arrange\",\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 929,\n            \"description\": \"Returns the amount of time the graph has been running in milliseconds\",\n            \"itemtype\": \"method\",\n            \"name\": \"getTime\",\n            \"return\": {\n                \"description\": \"number of milliseconds the graph has been running\",\n                \"type\": \"Number\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 939,\n            \"description\": \"Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant\",\n            \"itemtype\": \"method\",\n            \"name\": \"getFixedTime\",\n            \"return\": {\n                \"description\": \"number of milliseconds the graph has been running\",\n                \"type\": \"Number\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 950,\n            \"description\": \"Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct\\nif the nodes are using graphical actions\",\n            \"itemtype\": \"method\",\n            \"name\": \"getElapsedTime\",\n            \"return\": {\n                \"description\": \"number of milliseconds it took the last cycle\",\n                \"type\": \"Number\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 962,\n            \"description\": \"Sends an event to all the nodes, useful to trigger stuff\",\n            \"itemtype\": \"method\",\n            \"name\": \"sendEventToAllNodes\",\n            \"params\": [\n                {\n                    \"name\": \"eventname\",\n                    \"description\": \"the name of the event (function to be called)\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"params\",\n                    \"description\": \"parameters in array format\",\n                    \"type\": \"Array\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1004,\n            \"description\": \"Adds a new node instasnce to this graph\",\n            \"itemtype\": \"method\",\n            \"name\": \"add\",\n            \"params\": [\n                {\n                    \"name\": \"node\",\n                    \"description\": \"the instance of the node\",\n                    \"type\": \"LGraphNode\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1068,\n            \"description\": \"Removes a node from the graph\",\n            \"itemtype\": \"method\",\n            \"name\": \"remove\",\n            \"params\": [\n                {\n                    \"name\": \"node\",\n                    \"description\": \"the instance of the node\",\n                    \"type\": \"LGraphNode\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1149,\n            \"description\": \"Returns a node by its id.\",\n            \"itemtype\": \"method\",\n            \"name\": \"getNodeById\",\n            \"params\": [\n                {\n                    \"name\": \"id\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1162,\n            \"description\": \"Returns a list of nodes that matches a class\",\n            \"itemtype\": \"method\",\n            \"name\": \"findNodesByClass\",\n            \"params\": [\n                {\n                    \"name\": \"classObject\",\n                    \"description\": \"the class itself (not an string)\",\n                    \"type\": \"Class\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"a list with all the nodes of this type\",\n                \"type\": \"Array\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1178,\n            \"description\": \"Returns a list of nodes that matches a type\",\n            \"itemtype\": \"method\",\n            \"name\": \"findNodesByType\",\n            \"params\": [\n                {\n                    \"name\": \"type\",\n                    \"description\": \"the name of the node type\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"a list with all the nodes of this type\",\n                \"type\": \"Array\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1195,\n            \"description\": \"Returns a list of nodes that matches a name\",\n            \"itemtype\": \"method\",\n            \"name\": \"findNodesByTitle\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"the name of the node to search\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"a list with all the nodes with this name\",\n                \"type\": \"Array\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1211,\n            \"description\": \"Returns the top-most node in this position of the canvas\",\n            \"itemtype\": \"method\",\n            \"name\": \"getNodeOnPos\",\n            \"params\": [\n                {\n                    \"name\": \"x\",\n                    \"description\": \"the x coordinate in canvas space\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"y\",\n                    \"description\": \"the y coordinate in canvas space\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"nodes_list\",\n                    \"description\": \"a list with all the nodes to search from, by default is all the nodes in the graph\",\n                    \"type\": \"Array\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"the node at this position or null\",\n                \"type\": \"LGraphNode\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1231,\n            \"description\": \"Returns the top-most group in that position\",\n            \"itemtype\": \"method\",\n            \"name\": \"getGroupOnPos\",\n            \"params\": [\n                {\n                    \"name\": \"x\",\n                    \"description\": \"the x coordinate in canvas space\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"y\",\n                    \"description\": \"the y coordinate in canvas space\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"the group or null\",\n                \"type\": \"LGraphGroup\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1251,\n            \"description\": \"Tell this graph it has a global graph input of this type\",\n            \"itemtype\": \"method\",\n            \"name\": \"addGlobalInput\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"type\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"value\",\n                    \"description\": \"[optional]\",\n                    \"type\": \"*\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1274,\n            \"description\": \"Assign a data to the global graph input\",\n            \"itemtype\": \"method\",\n            \"name\": \"setGlobalInputData\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"data\",\n                    \"description\": \"\",\n                    \"type\": \"*\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1288,\n            \"description\": \"Returns the current value of a global graph input\",\n            \"itemtype\": \"method\",\n            \"name\": \"getInputData\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"the data\",\n                \"type\": \"*\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1302,\n            \"description\": \"Changes the name of a global graph input\",\n            \"itemtype\": \"method\",\n            \"name\": \"renameInput\",\n            \"params\": [\n                {\n                    \"name\": \"old_name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"new_name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1333,\n            \"description\": \"Changes the type of a global graph input\",\n            \"itemtype\": \"method\",\n            \"name\": \"changeInputType\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"type\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1353,\n            \"description\": \"Removes a global graph input\",\n            \"itemtype\": \"method\",\n            \"name\": \"removeInput\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"type\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1375,\n            \"description\": \"Creates a global graph output\",\n            \"itemtype\": \"method\",\n            \"name\": \"addOutput\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"type\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"value\",\n                    \"description\": \"\",\n                    \"type\": \"*\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1394,\n            \"description\": \"Assign a data to the global output\",\n            \"itemtype\": \"method\",\n            \"name\": \"setOutputData\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"value\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1408,\n            \"description\": \"Returns the current value of a global graph output\",\n            \"itemtype\": \"method\",\n            \"name\": \"getOutputData\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"the data\",\n                \"type\": \"*\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1422,\n            \"description\": \"Renames a global graph output\",\n            \"itemtype\": \"method\",\n            \"name\": \"renameOutput\",\n            \"params\": [\n                {\n                    \"name\": \"old_name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"new_name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1450,\n            \"description\": \"Changes the type of a global graph output\",\n            \"itemtype\": \"method\",\n            \"name\": \"changeOutputType\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"type\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1470,\n            \"description\": \"Removes a global graph output\",\n            \"itemtype\": \"method\",\n            \"name\": \"removeOutput\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1514,\n            \"description\": \"returns if the graph is in live mode\",\n            \"itemtype\": \"method\",\n            \"name\": \"isLive\",\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1533,\n            \"description\": \"clears the triggered slot animation in all links (stop visual animation)\",\n            \"itemtype\": \"method\",\n            \"name\": \"clearTriggeredSlots\",\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1565,\n            \"description\": \"Destroys a link\",\n            \"itemtype\": \"method\",\n            \"name\": \"removeLink\",\n            \"params\": [\n                {\n                    \"name\": \"link_id\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1582,\n            \"description\": \"Creates a Object containing all the info about this graph, it can be serialized\",\n            \"itemtype\": \"method\",\n            \"name\": \"serialize\",\n            \"return\": {\n                \"description\": \"value of the node\",\n                \"type\": \"Object\"\n            },\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1619,\n            \"description\": \"Configure a graph from a JSON string\",\n            \"itemtype\": \"method\",\n            \"name\": \"configure\",\n            \"params\": [\n                {\n                    \"name\": \"str\",\n                    \"description\": \"configure a graph from a JSON string\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"returns\",\n                    \"description\": \"if there was any error parsing\",\n                    \"type\": \"Boolean\"\n                }\n            ],\n            \"class\": \"LGraph\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1881,\n            \"description\": \"configure a node from an object containing the serialized info\",\n            \"itemtype\": \"method\",\n            \"name\": \"configure\",\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 1949,\n            \"description\": \"serialize the content\",\n            \"itemtype\": \"method\",\n            \"name\": \"serialize\",\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2039,\n            \"description\": \"serialize and stringify\",\n            \"itemtype\": \"method\",\n            \"name\": \"toString\",\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2051,\n            \"description\": \"get the title string\",\n            \"itemtype\": \"method\",\n            \"name\": \"getTitle\",\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2064,\n            \"description\": \"sets the output data\",\n            \"itemtype\": \"method\",\n            \"name\": \"setOutputData\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"data\",\n                    \"description\": \"\",\n                    \"type\": \"*\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2100,\n            \"description\": \"sets the output data type, useful when you want to be able to overwrite the data type\",\n            \"itemtype\": \"method\",\n            \"name\": \"setOutputDataType\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"datatype\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2129,\n            \"description\": \"Retrieves the input data (data traveling through the connection) from one slot\",\n            \"itemtype\": \"method\",\n            \"name\": \"getInputData\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"force_update\",\n                    \"description\": \"if set to true it will force the connected node of this slot to output data into this link\",\n                    \"type\": \"Boolean\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"data or if it is not connected returns undefined\",\n                \"type\": \"*\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2165,\n            \"description\": \"Retrieves the input data type (in case this supports multiple input types)\",\n            \"itemtype\": \"method\",\n            \"name\": \"getInputDataType\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"datatype in string format\",\n                \"type\": \"String\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2191,\n            \"description\": \"Retrieves the input data from one slot using its name instead of slot number\",\n            \"itemtype\": \"method\",\n            \"name\": \"getInputDataByName\",\n            \"params\": [\n                {\n                    \"name\": \"slot_name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"force_update\",\n                    \"description\": \"if set to true it will force the connected node of this slot to output data into this link\",\n                    \"type\": \"Boolean\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"data or if it is not connected returns null\",\n                \"type\": \"*\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2207,\n            \"description\": \"tells you if there is a connection in one input slot\",\n            \"itemtype\": \"method\",\n            \"name\": \"isInputConnected\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"\",\n                \"type\": \"Boolean\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2220,\n            \"description\": \"tells you info about an input connection (which node, type, etc)\",\n            \"itemtype\": \"method\",\n            \"name\": \"getInputInfo\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"object or null { link: id, name: string, type: string or 0 }\",\n                \"type\": \"Object\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2235,\n            \"description\": \"returns the node connected in the input slot\",\n            \"itemtype\": \"method\",\n            \"name\": \"getInputNode\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"node or null\",\n                \"type\": \"LGraphNode\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2257,\n            \"description\": \"returns the value of an input with this name, otherwise checks if there is a property with that name\",\n            \"itemtype\": \"method\",\n            \"name\": \"getInputOrProperty\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"value\",\n                \"type\": \"*\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2284,\n            \"description\": \"tells you the last output data that went in that slot\",\n            \"itemtype\": \"method\",\n            \"name\": \"getOutputData\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"object or null\",\n                \"type\": \"Object\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2302,\n            \"description\": \"tells you info about an output connection (which node, type, etc)\",\n            \"itemtype\": \"method\",\n            \"name\": \"getOutputInfo\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"object or null { name: string, type: string, links: [ ids of links in number ] }\",\n                \"type\": \"Object\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2318,\n            \"description\": \"tells you if there is a connection in one output slot\",\n            \"itemtype\": \"method\",\n            \"name\": \"isOutputConnected\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"\",\n                \"type\": \"Boolean\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2331,\n            \"description\": \"tells you if there is any connection in the output slots\",\n            \"itemtype\": \"method\",\n            \"name\": \"isAnyOutputConnected\",\n            \"return\": {\n                \"description\": \"\",\n                \"type\": \"Boolean\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2347,\n            \"description\": \"retrieves all the nodes connected to this output slot\",\n            \"itemtype\": \"method\",\n            \"name\": \"getOutputNodes\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"\",\n                \"type\": \"Array\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2380,\n            \"description\": \"Triggers an event in this node, this will trigger any output with the same name\",\n            \"itemtype\": \"method\",\n            \"name\": \"trigger\",\n            \"params\": [\n                {\n                    \"name\": \"event\",\n                    \"description\": \"name ( \\\"on_play\\\", ... ) if action is equivalent to false then the event is send to all\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"param\",\n                    \"description\": \"\",\n                    \"type\": \"*\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2403,\n            \"description\": \"Triggers an slot event in this node\",\n            \"itemtype\": \"method\",\n            \"name\": \"triggerSlot\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"the index of the output slot\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"param\",\n                    \"description\": \"\",\n                    \"type\": \"*\"\n                },\n                {\n                    \"name\": \"link_id\",\n                    \"description\": \"[optional] in case you want to trigger and specific output link in a slot\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2453,\n            \"description\": \"clears the trigger slot animation\",\n            \"itemtype\": \"method\",\n            \"name\": \"clearTriggeredSlot\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"the index of the output slot\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"link_id\",\n                    \"description\": \"[optional] in case you want to trigger and specific output link in a slot\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2485,\n            \"description\": \"add a new property to this node\",\n            \"itemtype\": \"method\",\n            \"name\": \"addProperty\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"default_value\",\n                    \"description\": \"\",\n                    \"type\": \"*\"\n                },\n                {\n                    \"name\": \"type\",\n                    \"description\": \"string defining the output type (\\\"vec3\\\",\\\"number\\\",...)\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"extra_info\",\n                    \"description\": \"this can be used to have special properties of the property (like values, etc)\",\n                    \"type\": \"Object\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2511,\n            \"description\": \"add a new output slot to use in this node\",\n            \"itemtype\": \"method\",\n            \"name\": \"addOutput\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"type\",\n                    \"description\": \"string defining the output type (\\\"vec3\\\",\\\"number\\\",...)\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"extra_info\",\n                    \"description\": \"this can be used to have special properties of an output (label, special color, position, etc)\",\n                    \"type\": \"Object\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2535,\n            \"description\": \"add a new output slot to use in this node\",\n            \"itemtype\": \"method\",\n            \"name\": \"addOutputs\",\n            \"params\": [\n                {\n                    \"name\": \"array\",\n                    \"description\": \"of triplets like [[name,type,extra_info],[...]]\",\n                    \"type\": \"Array\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2561,\n            \"description\": \"remove an existing output slot\",\n            \"itemtype\": \"method\",\n            \"name\": \"removeOutput\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2590,\n            \"description\": \"add a new input slot to use in this node\",\n            \"itemtype\": \"method\",\n            \"name\": \"addInput\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"type\",\n                    \"description\": \"string defining the input type (\\\"vec3\\\",\\\"number\\\",...), it its a generic one use 0\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"extra_info\",\n                    \"description\": \"this can be used to have special properties of an input (label, color, position, etc)\",\n                    \"type\": \"Object\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2615,\n            \"description\": \"add several new input slots in this node\",\n            \"itemtype\": \"method\",\n            \"name\": \"addInputs\",\n            \"params\": [\n                {\n                    \"name\": \"array\",\n                    \"description\": \"of triplets like [[name,type,extra_info],[...]]\",\n                    \"type\": \"Array\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2641,\n            \"description\": \"remove an existing input slot\",\n            \"itemtype\": \"method\",\n            \"name\": \"removeInput\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2665,\n            \"description\": \"add an special connection to this node (used for special kinds of graphs)\",\n            \"itemtype\": \"method\",\n            \"name\": \"addConnection\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"type\",\n                    \"description\": \"string defining the input type (\\\"vec3\\\",\\\"number\\\",...)\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"pos\",\n                    \"description\": \"position of the connection inside the node\",\n                    \"type\": \"[x,y]\"\n                },\n                {\n                    \"name\": \"direction\",\n                    \"description\": \"if is input or output\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2686,\n            \"description\": \"computes the size of a node according to its inputs and output slots\",\n            \"itemtype\": \"method\",\n            \"name\": \"computeSize\",\n            \"params\": [\n                {\n                    \"name\": \"minHeight\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"the total size\",\n                \"type\": \"Number\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2753,\n            \"description\": \"Allows to pass\",\n            \"itemtype\": \"method\",\n            \"name\": \"addWidget\",\n            \"return\": {\n                \"description\": \"the created widget\",\n                \"type\": \"Object\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2791,\n            \"description\": \"returns the bounding of the object, used for rendering purposes\\nbounding is: [topleft_cornerx, topleft_cornery, width, height]\",\n            \"itemtype\": \"method\",\n            \"name\": \"getBounding\",\n            \"return\": {\n                \"description\": \"the total size\",\n                \"type\": \"Float32Array[4]\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2810,\n            \"description\": \"checks if a point is inside the shape of a node\",\n            \"itemtype\": \"method\",\n            \"name\": \"isPointInside\",\n            \"params\": [\n                {\n                    \"name\": \"x\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"y\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"\",\n                \"type\": \"Boolean\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2836,\n            \"description\": \"checks if a point is inside a node slot, and returns info about which slot\",\n            \"itemtype\": \"method\",\n            \"name\": \"getSlotInPosition\",\n            \"params\": [\n                {\n                    \"name\": \"x\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"y\",\n                    \"description\": \"\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }\",\n                \"type\": \"Object\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2868,\n            \"description\": \"returns the input slot with a given name (used for dynamic slots), -1 if not found\",\n            \"itemtype\": \"method\",\n            \"name\": \"findInputSlot\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"the name of the slot\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"the slot (-1 if not found)\",\n                \"type\": \"Number\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2884,\n            \"description\": \"returns the output slot with a given name (used for dynamic slots), -1 if not found\",\n            \"itemtype\": \"method\",\n            \"name\": \"findOutputSlot\",\n            \"params\": [\n                {\n                    \"name\": \"name\",\n                    \"description\": \"the name of the slot\",\n                    \"type\": \"String\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"the slot (-1 if not found)\",\n                \"type\": \"Number\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 2899,\n            \"description\": \"connect this node output to the input of another node\",\n            \"itemtype\": \"method\",\n            \"name\": \"connect\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"(could be the number of the slot or the string with the name of the slot)\",\n                    \"type\": \"Number_or_string\"\n                },\n                {\n                    \"name\": \"node\",\n                    \"description\": \"the target node\",\n                    \"type\": \"LGraphNode\"\n                },\n                {\n                    \"name\": \"target_slot\",\n                    \"description\": \"the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)\",\n                    \"type\": \"Number_or_string\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"the link_info is created, otherwise null\",\n                \"type\": \"Object\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 3024,\n            \"description\": \"disconnect one output to an specific node\",\n            \"itemtype\": \"method\",\n            \"name\": \"disconnectOutput\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"(could be the number of the slot or the string with the name of the slot)\",\n                    \"type\": \"Number_or_string\"\n                },\n                {\n                    \"name\": \"target_node\",\n                    \"description\": \"the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]\",\n                    \"type\": \"LGraphNode\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"if it was disconnected succesfully\",\n                \"type\": \"Boolean\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 3132,\n            \"description\": \"disconnect one input\",\n            \"itemtype\": \"method\",\n            \"name\": \"disconnectInput\",\n            \"params\": [\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"(could be the number of the slot or the string with the name of the slot)\",\n                    \"type\": \"Number_or_string\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"if it was disconnected succesfully\",\n                \"type\": \"Boolean\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 3206,\n            \"description\": \"returns the center of a connection point in canvas coords\",\n            \"itemtype\": \"method\",\n            \"name\": \"getConnectionPos\",\n            \"params\": [\n                {\n                    \"name\": \"is_input\",\n                    \"description\": \"true if if a input slot, false if it is an output\",\n                    \"type\": \"Boolean\"\n                },\n                {\n                    \"name\": \"slot\",\n                    \"description\": \"(could be the number of the slot or the string with the name of the slot)\",\n                    \"type\": \"Number_or_string\"\n                },\n                {\n                    \"name\": \"out\",\n                    \"description\": \"[optional] a place to store the output, to free garbage\",\n                    \"type\": \"Vec2\"\n                }\n            ],\n            \"return\": {\n                \"description\": \"the position\",\n                \"type\": \"[x,y]\"\n            },\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 3390,\n            \"description\": \"Collapse the node to make it smaller on the canvas\",\n            \"itemtype\": \"method\",\n            \"name\": \"collapse\",\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 3406,\n            \"description\": \"Forces the node to do not move or realign on Z\",\n            \"itemtype\": \"method\",\n            \"name\": \"pin\",\n            \"class\": \"LGraphNode\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 3837,\n            \"description\": \"clears all the data inside\",\n            \"itemtype\": \"method\",\n            \"name\": \"clear\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 3880,\n            \"description\": \"assigns a graph, you can reasign graphs to the same canvas\",\n            \"itemtype\": \"method\",\n            \"name\": \"setGraph\",\n            \"params\": [\n                {\n                    \"name\": \"graph\",\n                    \"description\": \"\",\n                    \"type\": \"LGraph\"\n                }\n            ],\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 3911,\n            \"description\": \"opens a graph contained inside a node in the current graph\",\n            \"itemtype\": \"method\",\n            \"name\": \"openSubgraph\",\n            \"params\": [\n                {\n                    \"name\": \"graph\",\n                    \"description\": \"\",\n                    \"type\": \"LGraph\"\n                }\n            ],\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 3938,\n            \"description\": \"closes a subgraph contained inside a node\",\n            \"itemtype\": \"method\",\n            \"name\": \"closeSubgraph\",\n            \"params\": [\n                {\n                    \"name\": \"assigns\",\n                    \"description\": \"a graph\",\n                    \"type\": \"LGraph\"\n                }\n            ],\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 3961,\n            \"description\": \"assigns a canvas\",\n            \"itemtype\": \"method\",\n            \"name\": \"setCanvas\",\n            \"params\": [\n                {\n                    \"name\": \"assigns\",\n                    \"description\": \"a canvas (also accepts the ID of the element (not a selector)\",\n                    \"type\": \"Canvas\"\n                }\n            ],\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4038,\n            \"description\": \"binds mouse, keyboard, touch and drag events to the canvas\",\n            \"itemtype\": \"method\",\n            \"name\": \"bindEvents\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4090,\n            \"description\": \"unbinds mouse events from the canvas\",\n            \"itemtype\": \"method\",\n            \"name\": \"unbindEvents\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4138,\n            \"description\": \"this function allows to render the canvas using WebGL instead of Canvas2D\\nthis is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL\",\n            \"itemtype\": \"method\",\n            \"name\": \"enableWebGL\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4180,\n            \"description\": \"Used to attach the canvas in a popup\",\n            \"itemtype\": \"method\",\n            \"name\": \"getCanvasWindow\",\n            \"return\": {\n                \"description\": \"returns the window where the canvas is attached (the DOM root node)\",\n                \"type\": \"Window\"\n            },\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4194,\n            \"description\": \"starts rendering the content of the canvas when needed\",\n            \"itemtype\": \"method\",\n            \"name\": \"startRendering\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4218,\n            \"description\": \"stops rendering the content of the canvas (to save resources)\",\n            \"itemtype\": \"method\",\n            \"name\": \"stopRendering\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4501,\n            \"description\": \"Called when a mouse move event has to be processed\",\n            \"itemtype\": \"method\",\n            \"name\": \"processMouseMove\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4675,\n            \"description\": \"Called when a mouse up event has to be processed\",\n            \"itemtype\": \"method\",\n            \"name\": \"processMouseUp\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4847,\n            \"description\": \"Called when a mouse wheel event has to be processed\",\n            \"itemtype\": \"method\",\n            \"name\": \"processMouseWheel\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4876,\n            \"description\": \"retuns true if a position (in graph space) is on top of a node little corner box\",\n            \"itemtype\": \"method\",\n            \"name\": \"isOverNodeBox\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4888,\n            \"description\": \"retuns true if a position (in graph space) is on top of a node input slot\",\n            \"itemtype\": \"method\",\n            \"name\": \"isOverNodeInput\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 4917,\n            \"description\": \"process a key event\",\n            \"itemtype\": \"method\",\n            \"name\": \"processKey\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5074,\n            \"description\": \"process a item drop event on top the canvas\",\n            \"itemtype\": \"method\",\n            \"name\": \"processDrop\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5192,\n            \"description\": \"selects a given node (or adds it to the current selection)\",\n            \"itemtype\": \"method\",\n            \"name\": \"selectNode\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5204,\n            \"description\": \"selects several nodes (or adds them to the current selection)\",\n            \"itemtype\": \"method\",\n            \"name\": \"selectNodes\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5242,\n            \"description\": \"removes a node from the current selection\",\n            \"itemtype\": \"method\",\n            \"name\": \"deselectNode\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5268,\n            \"description\": \"removes all nodes from the current selection\",\n            \"itemtype\": \"method\",\n            \"name\": \"deselectAllNodes\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5291,\n            \"description\": \"deletes all nodes in the current selection from the graph\",\n            \"itemtype\": \"method\",\n            \"name\": \"deleteSelectedNodes\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5308,\n            \"description\": \"centers the camera on a given node\",\n            \"itemtype\": \"method\",\n            \"name\": \"centerOnNode\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5319,\n            \"description\": \"adds some useful properties to a mouse event, like the position in graph coordinates\",\n            \"itemtype\": \"method\",\n            \"name\": \"adjustMouseEvent\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5347,\n            \"description\": \"changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom\",\n            \"itemtype\": \"method\",\n            \"name\": \"setZoom\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5378,\n            \"description\": \"converts a coordinate from graph coordinates to canvas2D coordinates\",\n            \"itemtype\": \"method\",\n            \"name\": \"convertOffsetToCanvas\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5387,\n            \"description\": \"converts a coordinate from Canvas2D coordinates to graph space\",\n            \"itemtype\": \"method\",\n            \"name\": \"convertCanvasToOffset\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5403,\n            \"description\": \"brings a node to front (above all other nodes)\",\n            \"itemtype\": \"method\",\n            \"name\": \"bringToFront\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5416,\n            \"description\": \"sends a node to the back (below all other nodes)\",\n            \"itemtype\": \"method\",\n            \"name\": \"sendToBack\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5436,\n            \"description\": \"checks which nodes are visible (inside the camera area)\",\n            \"itemtype\": \"method\",\n            \"name\": \"computeVisibleNodes\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5461,\n            \"description\": \"renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)\",\n            \"itemtype\": \"method\",\n            \"name\": \"draw\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5488,\n            \"description\": \"draws the front canvas (the one containing all the nodes)\",\n            \"itemtype\": \"method\",\n            \"name\": \"drawFrontCanvas\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5632,\n            \"description\": \"draws some useful stats in the corner of the canvas\",\n            \"itemtype\": \"method\",\n            \"name\": \"renderInfo\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5659,\n            \"description\": \"draws the back canvas (the one containing the background and the connections)\",\n            \"itemtype\": \"method\",\n            \"name\": \"drawBackCanvas\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 5807,\n            \"description\": \"draws the given node inside the canvas\",\n            \"itemtype\": \"method\",\n            \"name\": \"drawNode\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 6115,\n            \"description\": \"draws the shape of the given node in the canvas\",\n            \"itemtype\": \"method\",\n            \"name\": \"drawNodeShape\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 6311,\n            \"description\": \"draws every connection visible in the canvas\\nOPTIMIZE THIS: precatch connections position instead of recomputing them every time\",\n            \"itemtype\": \"method\",\n            \"name\": \"drawConnections\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 6398,\n            \"description\": \"draws a link between two points\",\n            \"itemtype\": \"method\",\n            \"name\": \"renderLink\",\n            \"params\": [\n                {\n                    \"name\": \"a\",\n                    \"description\": \"start pos\",\n                    \"type\": \"Vec2\"\n                },\n                {\n                    \"name\": \"b\",\n                    \"description\": \"end pos\",\n                    \"type\": \"Vec2\"\n                },\n                {\n                    \"name\": \"link\",\n                    \"description\": \"the link object with all the link info\",\n                    \"type\": \"Object\"\n                },\n                {\n                    \"name\": \"skip_border\",\n                    \"description\": \"ignore the shadow of the link\",\n                    \"type\": \"Boolean\"\n                },\n                {\n                    \"name\": \"flow\",\n                    \"description\": \"show flow animation (for events)\",\n                    \"type\": \"Boolean\"\n                },\n                {\n                    \"name\": \"color\",\n                    \"description\": \"the color for the link\",\n                    \"type\": \"String\"\n                },\n                {\n                    \"name\": \"start_dir\",\n                    \"description\": \"the direction enum\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"end_dir\",\n                    \"description\": \"the direction enum\",\n                    \"type\": \"Number\"\n                },\n                {\n                    \"name\": \"num_sublines\",\n                    \"description\": \"number of sublines (useful to represent vec3 or rgb)\",\n                    \"type\": \"Number\"\n                }\n            ],\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 6663,\n            \"description\": \"draws the widgets stored inside a node\",\n            \"itemtype\": \"method\",\n            \"name\": \"drawNodeWidgets\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 6813,\n            \"description\": \"process an event on widgets\",\n            \"itemtype\": \"method\",\n            \"name\": \"processNodeWidgets\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 6924,\n            \"description\": \"draws every group area in the background\",\n            \"itemtype\": \"method\",\n            \"name\": \"drawGroups\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 6979,\n            \"description\": \"resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode\",\n            \"itemtype\": \"method\",\n            \"name\": \"resize\",\n            \"class\": \"LGraphCanvas\"\n        },\n        {\n            \"file\": \"../src/litegraph.js\",\n            \"line\": 7002,\n            \"description\": \"switches to live mode (node shapes are not rendered, only the content)\\nthis feature was designed when graphs where meant to create user interfaces\",\n            \"itemtype\": \"method\",\n            \"name\": \"switchLiveMode\",\n            \"class\": \"LGraphCanvas\"\n        }\n    ],\n    \"warnings\": []\n}"
  },
  {
    "path": "doc/elements/index.html",
    "content": "<!doctype html>\n<html>\n    <head>\n        <title>Redirector</title>\n        <meta http-equiv=\"refresh\" content=\"0;url=../\">\n    </head>\n    <body>\n        <a href=\"../\">Click here to redirect</a>\n    </body>\n</html>\n"
  },
  {
    "path": "doc/files/.._src_litegraph.js.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>../src/litegraph.js</title>\n    <link rel=\"stylesheet\" href=\"http://yui.yahooapis.com/3.9.1/build/cssgrids/cssgrids-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/vendor/prettify/prettify-min.css\">\n    <link rel=\"stylesheet\" href=\"../assets/css/main.css\" id=\"site_styles\">\n    <link rel=\"icon\" href=\"../assets/favicon.ico\">\n    <script src=\"http://yui.yahooapis.com/combo?3.9.1/build/yui/yui-min.js\"></script>\n</head>\n<body class=\"yui3-skin-sam\">\n\n<div id=\"doc\">\n    <div id=\"hd\" class=\"yui3-g header\">\n        <div class=\"yui3-u-3-4\">\n                <h1><img src=\"../assets/css/logo.png\" title=\"\" width=\"117\" height=\"52\"></h1>\n        </div>\n        <div class=\"yui3-u-1-4 version\">\n            <em>API Docs for: </em>\n        </div>\n    </div>\n    <div id=\"bd\" class=\"yui3-g\">\n\n        <div class=\"yui3-u-1-4\">\n            <div id=\"docs-sidebar\" class=\"sidebar apidocs\">\n                <div id=\"api-list\">\n                    <h2 class=\"off-left\">APIs</h2>\n                    <div id=\"api-tabview\" class=\"tabview\">\n                        <ul class=\"tabs\">\n                            <li><a href=\"#api-classes\">Classes</a></li>\n                            <li><a href=\"#api-modules\">Modules</a></li>\n                        </ul>\n                \n                        <div id=\"api-tabview-filter\">\n                            <input type=\"search\" id=\"api-filter\" placeholder=\"Type to filter APIs\">\n                        </div>\n                \n                        <div id=\"api-tabview-panel\">\n                            <ul id=\"api-classes\" class=\"apis classes\">\n                                <li><a href=\"../classes/ContextMenu.html\">ContextMenu</a></li>\n                                <li><a href=\"../classes/LGraph.html\">LGraph</a></li>\n                                <li><a href=\"../classes/LGraphCanvas.html\">LGraphCanvas</a></li>\n                                <li><a href=\"../classes/LGraphNode.html\">LGraphNode</a></li>\n                                <li><a href=\"../classes/LiteGraph.html\">LiteGraph</a></li>\n                            </ul>\n                \n                \n                            <ul id=\"api-modules\" class=\"apis modules\">\n                            </ul>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <div class=\"yui3-u-3-4\">\n                <div id=\"api-options\">\n                    Show:\n                    <label for=\"api-show-inherited\">\n                        <input type=\"checkbox\" id=\"api-show-inherited\" checked>\n                        Inherited\n                    </label>\n            \n                    <label for=\"api-show-protected\">\n                        <input type=\"checkbox\" id=\"api-show-protected\">\n                        Protected\n                    </label>\n            \n                    <label for=\"api-show-private\">\n                        <input type=\"checkbox\" id=\"api-show-private\">\n                        Private\n                    </label>\n                    <label for=\"api-show-deprecated\">\n                        <input type=\"checkbox\" id=\"api-show-deprecated\">\n                        Deprecated\n                    </label>\n            \n                </div>\n            \n            <div class=\"apidocs\">\n                <div id=\"docs-main\">\n                    <div class=\"content\">\n<h1 class=\"file-heading\">File: ../src/litegraph.js</h1>\n\n<div class=\"file\">\n    <pre class=\"code prettyprint linenums\">\n(function(global){\n// *************************************************************\n//   LiteGraph CLASS                                     *******\n// *************************************************************\n\n/**\n* The Global Scope. It contains all the registered node classes.\n*\n* @class LiteGraph\n* @constructor\n*/\n\nvar LiteGraph = global.LiteGraph = {\n\n\tVERSION: 0.4,\n\n\tCANVAS_GRID_SIZE: 10,\n\t\n\tNODE_TITLE_HEIGHT: 30,\n\tNODE_TITLE_TEXT_Y: 20,\n\tNODE_SLOT_HEIGHT: 20,\n\tNODE_WIDGET_HEIGHT: 20,\n\tNODE_WIDTH: 140,\n\tNODE_MIN_WIDTH: 50,\n\tNODE_COLLAPSED_RADIUS: 10,\n\tNODE_COLLAPSED_WIDTH: 80,\n\tNODE_TITLE_COLOR: &quot;#999&quot;,\n\tNODE_TEXT_SIZE: 14,\n\tNODE_TEXT_COLOR: &quot;#AAA&quot;,\n\tNODE_SUBTEXT_SIZE: 12,\n\tNODE_DEFAULT_COLOR: &quot;#333&quot;,\n\tNODE_DEFAULT_BGCOLOR: &quot;#353535&quot;,\n\tNODE_DEFAULT_BOXCOLOR: &quot;#666&quot;,\n\tNODE_DEFAULT_SHAPE: &quot;box&quot;,\n\tDEFAULT_SHADOW_COLOR: &quot;rgba(0,0,0,0.5)&quot;,\n\tDEFAULT_GROUP_FONT: 24,\n\n\tLINK_COLOR: &quot;#9A9&quot;,\n\tEVENT_LINK_COLOR: &quot;#A86&quot;,\n\tCONNECTING_LINK_COLOR: &quot;#AFA&quot;,\n\n\tMAX_NUMBER_OF_NODES: 1000, //avoid infinite loops\n\tDEFAULT_POSITION: [100,100],//default node position\n\tVALID_SHAPES: [&quot;default&quot;,&quot;box&quot;,&quot;round&quot;,&quot;card&quot;], //,&quot;circle&quot;\n\n\t//shapes are used for nodes but also for slots\n\tBOX_SHAPE: 1,\n\tROUND_SHAPE: 2,\n\tCIRCLE_SHAPE: 3,\n\tCARD_SHAPE: 4,\n\tARROW_SHAPE: 5,\n\n\t//enums\n\tINPUT: 1,\n\tOUTPUT: 2,\n\n\tEVENT: -1, //for outputs\n\tACTION: -1, //for inputs\n\n\tALWAYS: 0,\n\tON_EVENT: 1,\n\tNEVER: 2,\n\tON_TRIGGER: 3,\n\n\tUP: 1,\n\tDOWN:2,\n\tLEFT:3,\n\tRIGHT:4,\n\tCENTER:5,\n\n\tSTRAIGHT_LINK: 0,\n\tLINEAR_LINK: 1,\n\tSPLINE_LINK: 2,\n\n\tNORMAL_TITLE: 0,\n\tNO_TITLE: 1,\n\tTRANSPARENT_TITLE: 2,\n\tAUTOHIDE_TITLE: 3,\n\n\tproxy: null, //used to redirect calls\n\tnode_images_path: &quot;&quot;,\n\n\tdebug: false,\n\tcatch_exceptions: true,\n\tthrow_errors: true,\n\tallow_scripts: false, //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits\n\tregistered_node_types: {}, //nodetypes by string\n\tnode_types_by_file_extension: {}, //used for droping files in the canvas\n\tNodes: {}, //node types by classname\n\n\tsearchbox_extras: {}, //used to add extra features to the search box\n\n\t/**\n\t* Register a node class so it can be listed when the user wants to create a new one\n\t* @method registerNodeType\n\t* @param {String} type name of the node and path\n\t* @param {Class} base_class class containing the structure of a node\n\t*/\n\n\tregisterNodeType: function(type, base_class)\n\t{\n\t\tif(!base_class.prototype)\n\t\t\tthrow(&quot;Cannot register a simple object, it must be a class with a prototype&quot;);\n\t\tbase_class.type = type;\n\n\t\tif(LiteGraph.debug)\n\t\t\tconsole.log(&quot;Node registered: &quot; + type);\n\n\t\tvar categories = type.split(&quot;/&quot;);\n\t\tvar classname = base_class.name;\n\n\t\tvar pos = type.lastIndexOf(&quot;/&quot;);\n\t\tbase_class.category = type.substr(0,pos);\n\n\t\tif(!base_class.title)\n\t\t\tbase_class.title = classname;\n\t\t//info.name = name.substr(pos+1,name.length - pos);\n\n\t\t//extend class\n\t\tif(base_class.prototype) //is a class\n\t\t\tfor(var i in LGraphNode.prototype)\n\t\t\t\tif(!base_class.prototype[i])\n\t\t\t\t\tbase_class.prototype[i] = LGraphNode.prototype[i];\n\n\t\tObject.defineProperty( base_class.prototype, &quot;shape&quot;,{\n\t\t\tset: function(v) {\n\t\t\t\tswitch(v)\n\t\t\t\t{\n\t\t\t\t\tcase &quot;default&quot;: delete this._shape; break;\n\t\t\t\t\tcase &quot;box&quot;: this._shape = LiteGraph.BOX_SHAPE; break;\n\t\t\t\t\tcase &quot;round&quot;: this._shape = LiteGraph.ROUND_SHAPE; break;\n\t\t\t\t\tcase &quot;circle&quot;: this._shape = LiteGraph.CIRCLE_SHAPE; break;\n\t\t\t\t\tcase &quot;card&quot;: this._shape = LiteGraph.CARD_SHAPE; break;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tthis._shape = v;\n\t\t\t\t}\n\t\t\t},\n\t\t\tget: function(v)\n\t\t\t{\n\t\t\t\treturn this._shape;\n\t\t\t},\n\t\t\tenumerable: true\n\t\t});\n\n\t\tthis.registered_node_types[ type ] = base_class;\n\t\tif(base_class.constructor.name)\n\t\t\tthis.Nodes[ classname ] = base_class;\n\n\t\t//warnings\n\t\tif(base_class.prototype.onPropertyChange)\n\t\t\tconsole.warn(&quot;LiteGraph node class &quot; + type + &quot; has onPropertyChange method, it must be called onPropertyChanged with d at the end&quot;);\n\n\t\tif( base_class.supported_extensions )\n\t\t{\n\t\t\tfor(var i in base_class.supported_extensions )\n\t\t\t\tthis.node_types_by_file_extension[ base_class.supported_extensions[i].toLowerCase() ] = base_class;\n\t\t}\n\t},\n\n\t/**\n\t* Create a new node type by passing a function, it wraps it with a propper class and generates inputs according to the parameters of the function.\n\t* Useful to wrap simple methods that do not require properties, and that only process some input to generate an output.\n\t* @method wrapFunctionAsNode\n\t* @param {String} name node name with namespace (p.e.: &#x27;math/sum&#x27;)\n\t* @param {Function} func\n\t* @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type\n\t* @param {String} return_type [optional] string with the return type, otherwise it will be generic\n\t* @param {Object} properties [optional] properties to be configurable\n\t*/\n\twrapFunctionAsNode: function( name, func, param_types, return_type, properties )\n\t{\n\t\tvar params = Array(func.length);\n\t\tvar code = &quot;&quot;;\n\t\tvar names = LiteGraph.getParameterNames( func );\n\t\tfor(var i = 0; i &lt; names.length; ++i)\n\t\t\tcode += &quot;this.addInput(&#x27;&quot;+names[i]+&quot;&#x27;,&quot;+(param_types &amp;&amp; param_types[i] ? &quot;&#x27;&quot; + param_types[i] + &quot;&#x27;&quot; : &quot;0&quot;) + &quot;);\\n&quot;;\n\t\tcode += &quot;this.addOutput(&#x27;out&#x27;,&quot;+( return_type ? &quot;&#x27;&quot; + return_type + &quot;&#x27;&quot; : 0 )+&quot;);\\n&quot;;\n\t\tif(properties)\n\t\t\tcode += &quot;this.properties = &quot; + JSON.stringify(properties) + &quot;;\\n&quot;;\n\t\tvar classobj = Function(code);\n\t\tclassobj.title = name.split(&quot;/&quot;).pop();\n\t\tclassobj.desc = &quot;Generated from &quot; + func.name;\n\t\tclassobj.prototype.onExecute = function onExecute()\n\t\t{\n\t\t\tfor(var i = 0; i &lt; params.length; ++i)\n\t\t\t\tparams[i] = this.getInputData(i);\n\t\t\tvar r = func.apply( this, params );\n\t\t\tthis.setOutputData(0,r);\n\t\t}\n\t\tthis.registerNodeType( name, classobj );\n\t},\n\n\t/**\n\t* Adds this method to all nodetypes, existing and to be created\n\t* (You can add it to LGraphNode.prototype but then existing node types wont have it)\n\t* @method addNodeMethod\n\t* @param {Function} func\n\t*/\n\taddNodeMethod: function( name, func )\n\t{\n\t\tLGraphNode.prototype[name] = func;\n\t\tfor(var i in this.registered_node_types)\n\t\t{\n\t\t\tvar type = this.registered_node_types[i];\n\t\t\tif(type.prototype[name])\n\t\t\t\ttype.prototype[&quot;_&quot; + name] = type.prototype[name]; //keep old in case of replacing\n\t\t\ttype.prototype[name] = func;\n\t\t}\n\t},\n\n\t/**\n\t* Create a node of a given type with a name. The node is not attached to any graph yet.\n\t* @method createNode\n\t* @param {String} type full name of the node class. p.e. &quot;math/sin&quot;\n\t* @param {String} name a name to distinguish from other nodes\n\t* @param {Object} options to set options\n\t*/\n\n\tcreateNode: function( type, title, options )\n\t{\n\t\tvar base_class = this.registered_node_types[type];\n\t\tif (!base_class)\n\t\t{\n\t\t\tif(LiteGraph.debug)\n\t\t\t\tconsole.log(&quot;GraphNode type \\&quot;&quot; + type + &quot;\\&quot; not registered.&quot;);\n\t\t\treturn null;\n\t\t}\n\n\t\tvar prototype = base_class.prototype || base_class;\n\n\t\ttitle = title || base_class.title || type;\n\n\t\tvar node = null;\n\n\t\tif( LiteGraph.catch_exceptions )\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tnode = new base_class( title );\n\t\t\t}\n\t\t\tcatch (err)\n\t\t\t{\n\t\t\t\tconsole.error(err);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tnode = new base_class( title );\n\n\t\tnode.type = type;\n\n\t\tif(!node.title &amp;&amp; title) node.title = title;\n\t\tif(!node.properties) node.properties = {};\n\t\tif(!node.properties_info) node.properties_info = [];\n\t\tif(!node.flags) node.flags = {};\n\t\tif(!node.size) node.size = node.computeSize();\n\t\tif(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat();\n\t\tif(!node.mode) node.mode = LiteGraph.ALWAYS;\n\n\t\t//extra options\n\t\tif(options)\n\t\t{\n\t\t\tfor(var i in options)\n\t\t\t\tnode[i] = options[i];\n\t\t}\n\n\t\treturn node;\n\t},\n\n\t/**\n\t* Returns a registered node type with a given name\n\t* @method getNodeType\n\t* @param {String} type full name of the node class. p.e. &quot;math/sin&quot;\n\t* @return {Class} the node class\n\t*/\n\tgetNodeType: function(type)\n\t{\n\t\treturn this.registered_node_types[type];\n\t},\n\n\t/**\n\t* Returns a list of node types matching one category\n\t* @method getNodeType\n\t* @param {String} category category name\n\t* @return {Array} array with all the node classes\n\t*/\n\n\tgetNodeTypesInCategory: function( category, filter )\n\t{\n\t\tvar r = [];\n\t\tfor(var i in this.registered_node_types)\n\t\t{\n\t\t\tvar type = this.registered_node_types[i];\n\t\t\tif(filter &amp;&amp; type.filter &amp;&amp; type.filter != filter)\n\t\t\t\tcontinue;\n\n\t\t\tif(category == &quot;&quot; )\n\t\t\t{\n\t\t\t\tif (type.category == null)\n\t\t\t\t\tr.push(type);\n\t\t\t}\n\t\t\telse if (type.category == category)\n\t\t\t\tr.push(type);\n\t\t}\n\n\t\treturn r;\n\t},\n\n\t/**\n\t* Returns a list with all the node type categories\n\t* @method getNodeTypesCategories\n\t* @return {Array} array with all the names of the categories\n\t*/\n\n\tgetNodeTypesCategories: function()\n\t{\n\t\tvar categories = {&quot;&quot;:1};\n\t\tfor(var i in this.registered_node_types)\n\t\t\tif(this.registered_node_types[i].category &amp;&amp; !this.registered_node_types[i].skip_list)\n\t\t\t\tcategories[ this.registered_node_types[i].category ] = 1;\n\t\tvar result = [];\n\t\tfor(var i in categories)\n\t\t\tresult.push(i);\n\t\treturn result;\n\t},\n\n\t//debug purposes: reloads all the js scripts that matches a wilcard\n\treloadNodes: function (folder_wildcard)\n\t{\n\t\tvar tmp = document.getElementsByTagName(&quot;script&quot;);\n\t\t//weird, this array changes by its own, so we use a copy\n\t\tvar script_files = [];\n\t\tfor(var i in tmp)\n\t\t\tscript_files.push(tmp[i]);\n\n\n\t\tvar docHeadObj = document.getElementsByTagName(&quot;head&quot;)[0];\n\t\tfolder_wildcard = document.location.href + folder_wildcard;\n\n\t\tfor(var i in script_files)\n\t\t{\n\t\t\tvar src = script_files[i].src;\n\t\t\tif( !src || src.substr(0,folder_wildcard.length ) != folder_wildcard)\n\t\t\t\tcontinue;\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\tif(LiteGraph.debug)\n\t\t\t\t\tconsole.log(&quot;Reloading: &quot; + src);\n\t\t\t\tvar dynamicScript = document.createElement(&quot;script&quot;);\n\t\t\t\tdynamicScript.type = &quot;text/javascript&quot;;\n\t\t\t\tdynamicScript.src = src;\n\t\t\t\tdocHeadObj.appendChild(dynamicScript);\n\t\t\t\tdocHeadObj.removeChild(script_files[i]);\n\t\t\t}\n\t\t\tcatch (err)\n\t\t\t{\n\t\t\t\tif(LiteGraph.throw_errors)\n\t\t\t\t\tthrow err;\n\t\t\t\tif(LiteGraph.debug)\n\t\t\t\t\tconsole.log(&quot;Error while reloading &quot; + src);\n\t\t\t}\n\t\t}\n\n\t\tif(LiteGraph.debug)\n\t\t\tconsole.log(&quot;Nodes reloaded&quot;);\n\t},\n\n\t//separated just to improve if it doesnt work\n\tcloneObject: function(obj, target)\n\t{\n\t\tif(obj == null) return null;\n\t\tvar r = JSON.parse( JSON.stringify( obj ) );\n\t\tif(!target) return r;\n\n\t\tfor(var i in r)\n\t\t\ttarget[i] = r[i];\n\t\treturn target;\n\t},\n\n\tisValidConnection: function( type_a, type_b )\n\t{\n\t\tif( !type_a ||  //generic output\n\t\t\t!type_b || //generic input\n\t\t\ttype_a == type_b || //same type (is valid for triggers)\n\t\t\ttype_a == LiteGraph.EVENT &amp;&amp; type_b == LiteGraph.ACTION )\n        return true;\n\n\t\t// Enforce string type to handle toLowerCase call (-1 number not ok)\n\t\ttype_a = String(type_a); \n\t\ttype_b = String(type_b);\n\t\ttype_a = type_a.toLowerCase();\n\t\ttype_b = type_b.toLowerCase();\n\n\t\t// For nodes supporting multiple connection types\n\t\tif( type_a.indexOf(&quot;,&quot;) == -1 &amp;&amp; type_b.indexOf(&quot;,&quot;) == -1 )\n\t\t\treturn type_a == type_b;\n\n\t\t// Check all permutations to see if one is valid\n\t\tvar supported_types_a = type_a.split(&quot;,&quot;);\n\t\tvar supported_types_b = type_b.split(&quot;,&quot;);\n\t\tfor(var i = 0; i &lt; supported_types_a.length; ++i)\n\t\t\tfor(var j = 0; j &lt; supported_types_b.length; ++j)\n\t\t\t\tif( supported_types_a[i] == supported_types_b[j] )\n\t\t\t\t\treturn true;\n\n\t\treturn false;\n\t},\n\n\tregisterSearchboxExtra: function( node_type, description, data )\n\t{\n\t\tthis.searchbox_extras[ description ] = { type: node_type, desc: description, data: data };\n\t}\n};\n\n//timer that works everywhere\nif(typeof(performance) != &quot;undefined&quot;)\n\tLiteGraph.getTime = performance.now.bind(performance);\nelse if(typeof(Date) != &quot;undefined&quot; &amp;&amp; Date.now)\n\tLiteGraph.getTime = Date.now.bind(Date);\nelse if(typeof(process) != &quot;undefined&quot;)\n\tLiteGraph.getTime = function(){\n\t\tvar t = process.hrtime();\n\t\treturn t[0]*0.001 + t[1]*(1e-6);\n\t}\nelse\n  LiteGraph.getTime = function getTime() { return (new Date).getTime(); }\n\n\n\n\n\n\n//*********************************************************************************\n// LGraph CLASS\n//*********************************************************************************\n\n/**\n* LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.\n*\n* @class LGraph\n* @constructor\n* @param {Object} o data from previous serialization [optional]\n*/\n\nfunction LGraph( o )\n{\n\tif (LiteGraph.debug)\n\t\tconsole.log(&quot;Graph created&quot;);\n\tthis.list_of_graphcanvas = null;\n\tthis.clear();\n\n\tif(o)\n\t\tthis.configure(o);\n}\n\nglobal.LGraph = LiteGraph.LGraph = LGraph;\n\n//default supported types\nLGraph.supported_types = [&quot;number&quot;,&quot;string&quot;,&quot;boolean&quot;];\n\n//used to know which types of connections support this graph (some graphs do not allow certain types)\nLGraph.prototype.getSupportedTypes = function() { return this.supported_types || LGraph.supported_types; }\n\nLGraph.STATUS_STOPPED = 1;\nLGraph.STATUS_RUNNING = 2;\n\n/**\n* Removes all nodes from this graph\n* @method clear\n*/\n\nLGraph.prototype.clear = function()\n{\n\tthis.stop();\n\tthis.status = LGraph.STATUS_STOPPED;\n\n\tthis.last_node_id = 1;\n\tthis.last_link_id = 1;\n\n\tthis._version = -1; //used to detect changes\n\n\t//safe clear\n\tif(this._nodes)\n\tfor(var i = 0; i &lt; this._nodes.length; ++i)\n\t{\n\t\tvar node = this._nodes[i];\n\t\tif(node.onRemoved)\n\t\t\tnode.onRemoved();\n\t}\n\n\t//nodes\n\tthis._nodes = [];\n\tthis._nodes_by_id = {};\n\tthis._nodes_in_order = []; //nodes that are executable sorted in execution order\n\tthis._nodes_executable = null; //nodes that contain onExecute\n\n\t//other scene stuff\n\tthis._groups = [];\n\n\t//links\n\tthis.links = {}; //container with all the links\n\n\t//iterations\n\tthis.iteration = 0;\n\n\t//custom data\n\tthis.config = {};\n\n\t//timing\n\tthis.globaltime = 0;\n\tthis.runningtime = 0;\n\tthis.fixedtime =  0;\n\tthis.fixedtime_lapse = 0.01;\n\tthis.elapsed_time = 0.01;\n\tthis.last_update_time = 0;\n\tthis.starttime = 0;\n\n\tthis.catch_errors = true;\n\n\t//subgraph_data\n\tthis.inputs = {};\n\tthis.outputs = {};\n\n\t//notify canvas to redraw\n\tthis.change();\n\n\tthis.sendActionToCanvas(&quot;clear&quot;);\n}\n\n/**\n* Attach Canvas to this graph\n* @method attachCanvas\n* @param {GraphCanvas} graph_canvas\n*/\n\nLGraph.prototype.attachCanvas = function(graphcanvas)\n{\n\tif(graphcanvas.constructor != LGraphCanvas)\n\t\tthrow(&quot;attachCanvas expects a LGraphCanvas instance&quot;);\n\tif(graphcanvas.graph &amp;&amp; graphcanvas.graph != this)\n\t\tgraphcanvas.graph.detachCanvas( graphcanvas );\n\n\tgraphcanvas.graph = this;\n\tif(!this.list_of_graphcanvas)\n\t\tthis.list_of_graphcanvas = [];\n\tthis.list_of_graphcanvas.push(graphcanvas);\n}\n\n/**\n* Detach Canvas from this graph\n* @method detachCanvas\n* @param {GraphCanvas} graph_canvas\n*/\nLGraph.prototype.detachCanvas = function(graphcanvas)\n{\n\tif(!this.list_of_graphcanvas)\n\t\treturn;\n\n\tvar pos = this.list_of_graphcanvas.indexOf( graphcanvas );\n\tif(pos == -1)\n\t\treturn;\n\tgraphcanvas.graph = null;\n\tthis.list_of_graphcanvas.splice(pos,1);\n}\n\n/**\n* Starts running this graph every interval milliseconds.\n* @method start\n* @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate\n*/\n\nLGraph.prototype.start = function( interval )\n{\n\tif( this.status == LGraph.STATUS_RUNNING )\n\t\treturn;\n\tthis.status = LGraph.STATUS_RUNNING;\n\n\tif(this.onPlayEvent)\n\t\tthis.onPlayEvent();\n\n\tthis.sendEventToAllNodes(&quot;onStart&quot;);\n\n\t//launch\n\tthis.starttime = LiteGraph.getTime();\n\tthis.last_update_time = this.starttime;\n\tinterval = interval || 0;\n\tvar that = this;\n\n\tif(interval == 0 &amp;&amp; typeof(window) != &quot;undefined&quot; &amp;&amp; window.requestAnimationFrame )\n\t{\n\t\tfunction on_frame()\n\t\t{\n\t\t\tif(that.execution_timer_id != -1)\n\t\t\t\treturn;\n\t\t\twindow.requestAnimationFrame(on_frame);\n\t\t\tthat.runStep(1, !this.catch_errors );\n\t\t}\n\t\tthis.execution_timer_id = -1;\n\t\ton_frame();\n\t}\n\telse\n\t\tthis.execution_timer_id = setInterval( function() {\n\t\t\t//execute\n\t\t\tthat.runStep(1, !this.catch_errors );\n\t\t},interval);\n}\n\n/**\n* Stops the execution loop of the graph\n* @method stop execution\n*/\n\nLGraph.prototype.stop = function()\n{\n\tif( this.status == LGraph.STATUS_STOPPED )\n\t\treturn;\n\n\tthis.status = LGraph.STATUS_STOPPED;\n\n\tif(this.onStopEvent)\n\t\tthis.onStopEvent();\n\n\tif(this.execution_timer_id != null)\n\t{\n\t\tif( this.execution_timer_id != -1 )\n\t\t\tclearInterval(this.execution_timer_id);\n\t\tthis.execution_timer_id = null;\n\t}\n\n\tthis.sendEventToAllNodes(&quot;onStop&quot;);\n}\n\n/**\n* Run N steps (cycles) of the graph\n* @method runStep\n* @param {number} num number of steps to run, default is 1\n*/\n\nLGraph.prototype.runStep = function( num, do_not_catch_errors )\n{\n\tnum = num || 1;\n\n\tvar start = LiteGraph.getTime();\n\tthis.globaltime = 0.001 * (start - this.starttime);\n\n\tvar nodes = this._nodes_executable ? this._nodes_executable : this._nodes;\n\tif(!nodes)\n\t\treturn;\n\n\tif( do_not_catch_errors )\n\t{\n\t\t//iterations\n\t\tfor(var i = 0; i &lt; num; i++)\n\t\t{\n\t\t\tfor( var j = 0, l = nodes.length; j &lt; l; ++j )\n\t\t\t{\n\t\t\t\tvar node = nodes[j];\n\t\t\t\tif( node.mode == LiteGraph.ALWAYS &amp;&amp; node.onExecute )\n\t\t\t\t\tnode.onExecute();\n\t\t\t}\n\n\t\t\tthis.fixedtime += this.fixedtime_lapse;\n\t\t\tif( this.onExecuteStep )\n\t\t\t\tthis.onExecuteStep();\n\t\t}\n\n\t\tif( this.onAfterExecute )\n\t\t\tthis.onAfterExecute();\n\t}\n\telse\n\t{\n\t\ttry\n\t\t{\n\t\t\t//iterations\n\t\t\tfor(var i = 0; i &lt; num; i++)\n\t\t\t{\n\t\t\t\tfor( var j = 0, l = nodes.length; j &lt; l; ++j )\n\t\t\t\t{\n\t\t\t\t\tvar node = nodes[j];\n\t\t\t\t\tif( node.mode == LiteGraph.ALWAYS &amp;&amp; node.onExecute )\n\t\t\t\t\t\tnode.onExecute();\n\t\t\t\t}\n\n\t\t\t\tthis.fixedtime += this.fixedtime_lapse;\n\t\t\t\tif( this.onExecuteStep )\n\t\t\t\t\tthis.onExecuteStep();\n\t\t\t}\n\n\t\t\tif( this.onAfterExecute )\n\t\t\t\tthis.onAfterExecute();\n\t\t\tthis.errors_in_execution = false;\n\t\t}\n\t\tcatch (err)\n\t\t{\n\t\t\tthis.errors_in_execution = true;\n\t\t\tif(LiteGraph.throw_errors)\n\t\t\t\tthrow err;\n\t\t\tif(LiteGraph.debug)\n\t\t\t\tconsole.log(&quot;Error during execution: &quot; + err);\n\t\t\tthis.stop();\n\t\t}\n\t}\n\n\tvar now = LiteGraph.getTime();\n\tvar elapsed = now - start;\n\tif (elapsed == 0)\n\t\telapsed = 1;\n\tthis.execution_time = 0.001 * elapsed;\n\tthis.globaltime += 0.001 * elapsed;\n\tthis.iteration += 1;\n\tthis.elapsed_time = (now - this.last_update_time) * 0.001;\n\tthis.last_update_time = now;\n}\n\n/**\n* Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than\n* nodes with only inputs.\n* @method updateExecutionOrder\n*/\nLGraph.prototype.updateExecutionOrder = function()\n{\n\tthis._nodes_in_order = this.computeExecutionOrder( false );\n\tthis._nodes_executable = [];\n\tfor(var i = 0; i &lt; this._nodes_in_order.length; ++i)\n\t\tif( this._nodes_in_order[i].onExecute )\n\t\t\tthis._nodes_executable.push( this._nodes_in_order[i] );\n}\n\n//This is more internal, it computes the order and returns it\nLGraph.prototype.computeExecutionOrder = function( only_onExecute, set_level )\n{\n\tvar L = [];\n\tvar S = [];\n\tvar M = {};\n\tvar visited_links = {}; //to avoid repeating links\n\tvar remaining_links = {}; //to a\n\n\t//search for the nodes without inputs (starting nodes)\n\tfor (var i = 0, l = this._nodes.length; i &lt; l; ++i)\n\t{\n\t\tvar node = this._nodes[i];\n\t\tif( only_onExecute &amp;&amp; !node.onExecute )\n\t\t\tcontinue;\n\n\t\tM[node.id] = node; //add to pending nodes\n\n\t\tvar num = 0; //num of input connections\n\t\tif(node.inputs)\n\t\t\tfor(var j = 0, l2 = node.inputs.length; j &lt; l2; j++)\n\t\t\t\tif(node.inputs[j] &amp;&amp; node.inputs[j].link != null)\n\t\t\t\t\tnum += 1;\n\n\t\tif(num == 0) //is a starting node\n\t\t{\n\t\t\tS.push(node);\n\t\t\tif(set_level)\n\t\t\t\tnode._level = 1;\n\t\t}\n\t\telse //num of input links\n\t\t{\n\t\t\tif(set_level)\n\t\t\t\tnode._level = 0;\n\t\t\tremaining_links[node.id] = num;\n\t\t}\n\t}\n\n\twhile(true)\n\t{\n\t\tif(S.length == 0)\n\t\t\tbreak;\n\n\t\t//get an starting node\n\t\tvar node = S.shift();\n\t\tL.push(node); //add to ordered list\n\t\tdelete M[node.id]; //remove from the pending nodes\n\n\t\tif(!node.outputs)\n\t\t\tcontinue;\n\n\t\t//for every output\n\t\tfor(var i = 0; i &lt; node.outputs.length; i++)\n\t\t{\n\t\t\tvar output = node.outputs[i];\n\t\t\t//not connected\n\t\t\tif(output == null || output.links == null || output.links.length == 0)\n\t\t\t\tcontinue;\n\n\t\t\t//for every connection\n\t\t\tfor(var j = 0; j &lt; output.links.length; j++)\n\t\t\t{\n\t\t\t\tvar link_id = output.links[j];\n\t\t\t\tvar link = this.links[link_id];\n\t\t\t\tif(!link)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t//already visited link (ignore it)\n\t\t\t\tif(visited_links[ link.id ])\n\t\t\t\t\tcontinue;\n\n\t\t\t\tvar target_node = this.getNodeById( link.target_id );\n\t\t\t\tif(target_node == null)\n\t\t\t\t{\n\t\t\t\t\tvisited_links[ link.id ] = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif(set_level &amp;&amp; (!target_node._level || target_node._level &lt;= node._level))\n\t\t\t\t\ttarget_node._level = node._level + 1;\n\n\t\t\t\tvisited_links[link.id] = true; //mark as visited\n\t\t\t\tremaining_links[target_node.id] -= 1; //reduce the number of links remaining\n\t\t\t\tif (remaining_links[ target_node.id ] == 0)\n\t\t\t\t\tS.push(target_node); //if no more links, then add to starters array\n\t\t\t}\n\t\t}\n\t}\n\n\t//the remaining ones (loops)\n\tfor(var i in M)\n\t\tL.push( M[i] );\n\n\tif( L.length != this._nodes.length &amp;&amp; LiteGraph.debug )\n\t\tconsole.warn(&quot;something went wrong, nodes missing&quot;);\n\n\tvar l = L.length;\n\n\t//save order number in the node\n\tfor(var i = 0; i &lt; l; ++i)\n\t\tL[i].order = i;\n\n\t//sort now by priority\n\tL = L.sort(function(A,B){ \n\t\tvar Ap = A.constructor.priority || A.priority || 0;\n\t\tvar Bp = B.constructor.priority || B.priority || 0;\n\t\tif(Ap == Bp)\n\t\t\treturn A.order - B.order;\n\t\treturn Ap - Bp;\n\t});\n\n\t//save order number in the node, again...\n\tfor(var i = 0; i &lt; l; ++i)\n\t\tL[i].order = i;\n\n\treturn L;\n}\n\n/**\n* Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.\n* It doesnt include the node itself\n* @method getAncestors\n* @return {Array} an array with all the LGraphNodes that affect this node, in order of execution\n*/\nLGraph.prototype.getAncestors = function( node )\n{\n\tvar ancestors = [];\n\tvar pending = [node];\n\tvar visited = {};\n\n\twhile (pending.length)\n\t{\n\t\tvar current = pending.shift();\n\t\tif(!current.inputs)\n\t\t\tcontinue;\n\t\tif( !visited[ current.id ] &amp;&amp; current != node )\n\t\t{\n\t\t\tvisited[ current.id ] = true;\n\t\t\tancestors.push( current );\n\t\t}\n\n\t\tfor(var i = 0; i &lt; current.inputs.length;++i)\n\t\t{\n\t\t\tvar input = current.getInputNode(i);\n\t\t\tif( input &amp;&amp; ancestors.indexOf( input ) == -1)\n\t\t\t{\n\t\t\t\tpending.push( input );\n\t\t\t}\n\t\t}\n\t}\n\n\tancestors.sort(function(a,b){ return a.order - b.order;});\n\treturn ancestors;\n}\n\n/**\n* Positions every node in a more readable manner\n* @method arrange\n*/\nLGraph.prototype.arrange = function( margin )\n{\n\tmargin = margin || 40;\n\n\tvar nodes = this.computeExecutionOrder( false, true );\n\tvar columns = [];\n\tfor(var i = 0; i &lt; nodes.length; ++i)\n\t{\n\t\tvar node = nodes[i];\n\t\tvar col = node._level || 1;\n\t\tif(!columns[col])\n\t\t\tcolumns[col] = [];\n\t\tcolumns[col].push( node );\n\t}\n\n\tvar x = margin;\n\n\tfor(var i = 0; i &lt; columns.length; ++i)\n\t{\n\t\tvar column = columns[i];\n\t\tif(!column)\n\t\t\tcontinue;\n\t\tvar max_size = 100;\n\t\tvar y = margin;\n\t\tfor(var j = 0; j &lt; column.length; ++j)\n\t\t{\n\t\t\tvar node = column[j];\n\t\t\tnode.pos[0] = x;\n\t\t\tnode.pos[1] = y;\n\t\t\tif(node.size[0] &gt; max_size)\n\t\t\t\tmax_size = node.size[0];\n\t\t\ty += node.size[1] + margin;\n\t\t}\n\t\tx += max_size + margin;\n\t}\n\n\tthis.setDirtyCanvas(true,true);\n}\n\n\n/**\n* Returns the amount of time the graph has been running in milliseconds\n* @method getTime\n* @return {number} number of milliseconds the graph has been running\n*/\nLGraph.prototype.getTime = function()\n{\n\treturn this.globaltime;\n}\n\n/**\n* Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant\n* @method getFixedTime\n* @return {number} number of milliseconds the graph has been running\n*/\n\nLGraph.prototype.getFixedTime = function()\n{\n\treturn this.fixedtime;\n}\n\n/**\n* Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct\n* if the nodes are using graphical actions\n* @method getElapsedTime\n* @return {number} number of milliseconds it took the last cycle\n*/\n\nLGraph.prototype.getElapsedTime = function()\n{\n\treturn this.elapsed_time;\n}\n\n/**\n* Sends an event to all the nodes, useful to trigger stuff\n* @method sendEventToAllNodes\n* @param {String} eventname the name of the event (function to be called)\n* @param {Array} params parameters in array format\n*/\n\nLGraph.prototype.sendEventToAllNodes = function( eventname, params, mode )\n{\n\tmode = mode || LiteGraph.ALWAYS;\n\n\tvar nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes;\n\tif(!nodes)\n\t\treturn;\n\n\tfor( var j = 0, l = nodes.length; j &lt; l; ++j )\n\t{\n\t\tvar node = nodes[j];\n\t\tif( !node[eventname] || node.mode != mode )\n\t\t\tcontinue;\n\t\tif(params === undefined)\n\t\t\tnode[eventname]();\n\t\telse if(params &amp;&amp; params.constructor === Array)\n\t\t\tnode[eventname].apply( node, params );\n\t\telse\n\t\t\tnode[eventname](params);\n\t}\n}\n\nLGraph.prototype.sendActionToCanvas = function(action, params)\n{\n\tif(!this.list_of_graphcanvas)\n\t\treturn;\n\n\tfor(var i = 0; i &lt; this.list_of_graphcanvas.length; ++i)\n\t{\n\t\tvar c = this.list_of_graphcanvas[i];\n\t\tif( c[action] )\n\t\t\tc[action].apply(c, params);\n\t}\n}\n\n/**\n* Adds a new node instasnce to this graph\n* @method add\n* @param {LGraphNode} node the instance of the node\n*/\n\nLGraph.prototype.add = function( node, skip_compute_order)\n{\n\tif(!node)\n\t\treturn;\n\n\t//groups\n\tif( node.constructor === LGraphGroup )\n\t{\n\t\tthis._groups.push( node );\n\t\tthis.setDirtyCanvas(true);\n\t\tthis.change();\n\t\tnode.graph = this;\n\t\tthis._version++;\n\t\treturn;\n\t}\n\n\t//nodes\n\tif(node.id != -1 &amp;&amp; this._nodes_by_id[node.id] != null)\n\t{\n\t\tconsole.warn(&quot;LiteGraph: there is already a node with this ID, changing it&quot;);\n\t\tnode.id = ++this.last_node_id;\n\t}\n\n\tif(this._nodes.length &gt;= LiteGraph.MAX_NUMBER_OF_NODES)\n\t\tthrow(&quot;LiteGraph: max number of nodes in a graph reached&quot;);\n\n\t//give him an id\n\tif(node.id == null || node.id == -1)\n\t\tnode.id = ++this.last_node_id;\n\telse if (this.last_node_id &lt; node.id)\n\t\tthis.last_node_id = node.id;\n\n\n\tnode.graph = this;\n\tthis._version++;\n\n\tthis._nodes.push(node);\n\tthis._nodes_by_id[node.id] = node;\n\n\tif(node.onAdded)\n\t\tnode.onAdded( this );\n\n\tif(this.config.align_to_grid)\n\t\tnode.alignToGrid();\n\n\tif(!skip_compute_order)\n\t\tthis.updateExecutionOrder();\n\n\tif(this.onNodeAdded)\n\t\tthis.onNodeAdded(node);\n\n\n\tthis.setDirtyCanvas(true);\n\tthis.change();\n\n\treturn node; //to chain actions\n}\n\n/**\n* Removes a node from the graph\n* @method remove\n* @param {LGraphNode} node the instance of the node\n*/\n\nLGraph.prototype.remove = function(node)\n{\n\tif(node.constructor === LiteGraph.LGraphGroup)\n\t{\n\t\tvar index = this._groups.indexOf(node);\n\t\tif(index != -1)\n\t\t\tthis._groups.splice(index,1);\n\t\tnode.graph = null;\n\t\tthis._version++;\n\t\tthis.setDirtyCanvas(true,true);\n\t\tthis.change();\n\t\treturn;\n\t}\n\n\tif(this._nodes_by_id[node.id] == null)\n\t\treturn; //not found\n\n\tif(node.ignore_remove)\n\t\treturn; //cannot be removed\n\n\t//disconnect inputs\n\tif(node.inputs)\n\t\tfor(var i = 0; i &lt; node.inputs.length; i++)\n\t\t{\n\t\t\tvar slot = node.inputs[i];\n\t\t\tif(slot.link != null)\n\t\t\t\tnode.disconnectInput(i);\n\t\t}\n\n\t//disconnect outputs\n\tif(node.outputs)\n\t\tfor(var i = 0; i &lt; node.outputs.length; i++)\n\t\t{\n\t\t\tvar slot = node.outputs[i];\n\t\t\tif(slot.links != null &amp;&amp; slot.links.length)\n\t\t\t\tnode.disconnectOutput(i);\n\t\t}\n\n\t//node.id = -1; //why?\n\n\t//callback\n\tif(node.onRemoved)\n\t\tnode.onRemoved();\n\n\tnode.graph = null;\n\tthis._version++;\n\n\t//remove from canvas render\n\tif(this.list_of_graphcanvas)\n\t{\n\t\tfor(var i = 0; i &lt; this.list_of_graphcanvas.length; ++i)\n\t\t{\n\t\t\tvar canvas = this.list_of_graphcanvas[i];\n\t\t\tif(canvas.selected_nodes[node.id])\n\t\t\t\tdelete canvas.selected_nodes[node.id];\n\t\t\tif(canvas.node_dragged == node)\n\t\t\t\tcanvas.node_dragged = null;\n\t\t}\n\t}\n\n\t//remove from containers\n\tvar pos = this._nodes.indexOf(node);\n\tif(pos != -1)\n\t\tthis._nodes.splice(pos,1);\n\tdelete this._nodes_by_id[node.id];\n\n\tif(this.onNodeRemoved)\n\t\tthis.onNodeRemoved(node);\n\n\tthis.setDirtyCanvas(true,true);\n\tthis.change();\n\n\tthis.updateExecutionOrder();\n}\n\n/**\n* Returns a node by its id.\n* @method getNodeById\n* @param {Number} id\n*/\n\nLGraph.prototype.getNodeById = function( id )\n{\n\tif( id == null )\n\t\treturn null;\n\treturn this._nodes_by_id[ id ];\n}\n\n/**\n* Returns a list of nodes that matches a class\n* @method findNodesByClass\n* @param {Class} classObject the class itself (not an string)\n* @return {Array} a list with all the nodes of this type\n*/\n\nLGraph.prototype.findNodesByClass = function(classObject)\n{\n\tvar r = [];\n\tfor(var i = 0, l = this._nodes.length; i &lt; l; ++i)\n\t\tif(this._nodes[i].constructor === classObject)\n\t\t\tr.push(this._nodes[i]);\n\treturn r;\n}\n\n/**\n* Returns a list of nodes that matches a type\n* @method findNodesByType\n* @param {String} type the name of the node type\n* @return {Array} a list with all the nodes of this type\n*/\n\nLGraph.prototype.findNodesByType = function(type)\n{\n\tvar type = type.toLowerCase();\n\tvar r = [];\n\tfor(var i = 0, l = this._nodes.length; i &lt; l; ++i)\n\t\tif(this._nodes[i].type.toLowerCase() == type )\n\t\t\tr.push(this._nodes[i]);\n\treturn r;\n}\n\n/**\n* Returns a list of nodes that matches a name\n* @method findNodesByTitle\n* @param {String} name the name of the node to search\n* @return {Array} a list with all the nodes with this name\n*/\n\nLGraph.prototype.findNodesByTitle = function(title)\n{\n\tvar result = [];\n\tfor(var i = 0, l = this._nodes.length; i &lt; l; ++i)\n\t\tif(this._nodes[i].title == title)\n\t\t\tresult.push(this._nodes[i]);\n\treturn result;\n}\n\n/**\n* Returns the top-most node in this position of the canvas\n* @method getNodeOnPos\n* @param {number} x the x coordinate in canvas space\n* @param {number} y the y coordinate in canvas space\n* @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph\n* @return {LGraphNode} the node at this position or null\n*/\nLGraph.prototype.getNodeOnPos = function( x, y, nodes_list, margin )\n{\n\tnodes_list = nodes_list || this._nodes;\n\tfor (var i = nodes_list.length - 1; i &gt;= 0; i--)\n\t{\n\t\tvar n = nodes_list[i];\n\t\tif(n.isPointInside( x, y, margin ))\n\t\t\treturn n;\n\t}\n\treturn null;\n}\n\n/**\n* Returns the top-most group in that position\n* @method getGroupOnPos\n* @param {number} x the x coordinate in canvas space\n* @param {number} y the y coordinate in canvas space\n* @return {LGraphGroup} the group or null\n*/\nLGraph.prototype.getGroupOnPos = function(x,y)\n{\n\tfor (var i = this._groups.length - 1; i &gt;= 0; i--)\n\t{\n\t\tvar g = this._groups[i];\n\t\tif(g.isPointInside( x, y, 2, true ))\n\t\t\treturn g;\n\t}\n\treturn null;\n}\n\n// ********** GLOBALS *****************\n\n/**\n* Tell this graph it has a global graph input of this type\n* @method addGlobalInput\n* @param {String} name\n* @param {String} type\n* @param {*} value [optional]\n*/\nLGraph.prototype.addInput = function(name, type, value)\n{\n\tvar input = this.inputs[ name ];\n\tif( input ) //already exist\n\t\treturn;\n\n\tthis.inputs[ name ] = { name: name, type: type, value: value };\n\tthis._version++;\n\n\tif(this.onInputAdded)\n\t\tthis.onInputAdded(name, type);\n\n\tif(this.onInputsOutputsChange)\n\t\tthis.onInputsOutputsChange();\n}\n\n/**\n* Assign a data to the global graph input\n* @method setGlobalInputData\n* @param {String} name\n* @param {*} data\n*/\nLGraph.prototype.setInputData = function(name, data)\n{\n\tvar input = this.inputs[name];\n\tif (!input)\n\t\treturn;\n\tinput.value = data;\n}\n\n/**\n* Returns the current value of a global graph input\n* @method getInputData\n* @param {String} name\n* @return {*} the data\n*/\nLGraph.prototype.getInputData = function(name)\n{\n\tvar input = this.inputs[name];\n\tif (!input)\n\t\treturn null;\n\treturn input.value;\n}\n\n/**\n* Changes the name of a global graph input\n* @method renameInput\n* @param {String} old_name\n* @param {String} new_name\n*/\nLGraph.prototype.renameInput = function(old_name, name)\n{\n\tif(name == old_name)\n\t\treturn;\n\n\tif(!this.inputs[old_name])\n\t\treturn false;\n\n\tif(this.inputs[name])\n\t{\n\t\tconsole.error(&quot;there is already one input with that name&quot;);\n\t\treturn false;\n\t}\n\n\tthis.inputs[name] = this.inputs[old_name];\n\tdelete this.inputs[old_name];\n\tthis._version++;\n\n\tif(this.onInputRenamed)\n\t\tthis.onInputRenamed(old_name, name);\n\n\tif(this.onInputsOutputsChange)\n\t\tthis.onInputsOutputsChange();\n}\n\n/**\n* Changes the type of a global graph input\n* @method changeInputType\n* @param {String} name\n* @param {String} type\n*/\nLGraph.prototype.changeInputType = function(name, type)\n{\n\tif(!this.inputs[name])\n\t\treturn false;\n\n\tif(this.inputs[name].type &amp;&amp; this.inputs[name].type.toLowerCase() == type.toLowerCase() )\n\t\treturn;\n\n\tthis.inputs[name].type = type;\n\tthis._version++;\n\tif(this.onInputTypeChanged)\n\t\tthis.onInputTypeChanged(name, type);\n}\n\n/**\n* Removes a global graph input\n* @method removeInput\n* @param {String} name\n* @param {String} type\n*/\nLGraph.prototype.removeInput = function(name)\n{\n\tif(!this.inputs[name])\n\t\treturn false;\n\n\tdelete this.inputs[name];\n\tthis._version++;\n\n\tif(this.onInputRemoved)\n\t\tthis.onInputRemoved(name);\n\n\tif(this.onInputsOutputsChange)\n\t\tthis.onInputsOutputsChange();\n\treturn true;\n}\n\n/**\n* Creates a global graph output\n* @method addOutput\n* @param {String} name\n* @param {String} type\n* @param {*} value\n*/\nLGraph.prototype.addOutput = function(name, type, value)\n{\n\tthis.outputs[name] = { name: name, type: type, value: value };\n\tthis._version++;\n\n\tif(this.onOutputAdded)\n\t\tthis.onOutputAdded(name, type);\n\n\tif(this.onInputsOutputsChange)\n\t\tthis.onInputsOutputsChange();\n}\n\n/**\n* Assign a data to the global output\n* @method setOutputData\n* @param {String} name\n* @param {String} value\n*/\nLGraph.prototype.setOutputData = function(name, value)\n{\n\tvar output = this.outputs[ name ];\n\tif (!output)\n\t\treturn;\n\toutput.value = value;\n}\n\n/**\n* Returns the current value of a global graph output\n* @method getOutputData\n* @param {String} name\n* @return {*} the data\n*/\nLGraph.prototype.getOutputData = function(name)\n{\n\tvar output = this.outputs[name];\n\tif (!output)\n\t\treturn null;\n\treturn output.value;\n}\n\n/**\n* Renames a global graph output\n* @method renameOutput\n* @param {String} old_name\n* @param {String} new_name\n*/\nLGraph.prototype.renameOutput = function(old_name, name)\n{\n\tif(!this.outputs[old_name])\n\t\treturn false;\n\n\tif(this.outputs[name])\n\t{\n\t\tconsole.error(&quot;there is already one output with that name&quot;);\n\t\treturn false;\n\t}\n\n\tthis.outputs[name] = this.outputs[old_name];\n\tdelete this.outputs[old_name];\n\tthis._version++;\n\n\tif(this.onOutputRenamed)\n\t\tthis.onOutputRenamed(old_name, name);\n\n\tif(this.onInputsOutputsChange)\n\t\tthis.onInputsOutputsChange();\n}\n\n/**\n* Changes the type of a global graph output\n* @method changeOutputType\n* @param {String} name\n* @param {String} type\n*/\nLGraph.prototype.changeOutputType = function(name, type)\n{\n\tif(!this.outputs[name])\n\t\treturn false;\n\n\tif(this.outputs[name].type &amp;&amp; this.outputs[name].type.toLowerCase() == type.toLowerCase() )\n\t\treturn;\n\n\tthis.outputs[name].type = type;\n\tthis._version++;\n\tif(this.onOutputTypeChanged)\n\t\tthis.onOutputTypeChanged(name, type);\n}\n\n/**\n* Removes a global graph output\n* @method removeOutput\n* @param {String} name\n*/\nLGraph.prototype.removeOutput = function(name)\n{\n\tif(!this.outputs[name])\n\t\treturn false;\n\tdelete this.outputs[name];\n\tthis._version++;\n\n\tif(this.onOutputRemoved)\n\t\tthis.onOutputRemoved(name);\n\n\tif(this.onInputsOutputsChange)\n\t\tthis.onInputsOutputsChange();\n\treturn true;\n}\n\nLGraph.prototype.triggerInput = function(name,value)\n{\n\tvar nodes = this.findNodesByTitle(name);\n\tfor(var i = 0; i &lt; nodes.length; ++i)\n\t\tnodes[i].onTrigger(value);\n}\n\nLGraph.prototype.setCallback = function(name,func)\n{\n\tvar nodes = this.findNodesByTitle(name);\n\tfor(var i = 0; i &lt; nodes.length; ++i)\n\t\tnodes[i].setTrigger(func);\n}\n\n\nLGraph.prototype.connectionChange = function( node, link_info )\n{\n\tthis.updateExecutionOrder();\n\tif( this.onConnectionChange )\n\t\tthis.onConnectionChange( node );\n\tthis._version++;\n\tthis.sendActionToCanvas(&quot;onConnectionChange&quot;);\n}\n\n/**\n* returns if the graph is in live mode\n* @method isLive\n*/\n\nLGraph.prototype.isLive = function()\n{\n\tif(!this.list_of_graphcanvas)\n\t\treturn false;\n\n\tfor(var i = 0; i &lt; this.list_of_graphcanvas.length; ++i)\n\t{\n\t\tvar c = this.list_of_graphcanvas[i];\n\t\tif(c.live_mode)\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n* clears the triggered slot animation in all links (stop visual animation)\n* @method clearTriggeredSlots\n*/\nLGraph.prototype.clearTriggeredSlots = function()\n{\n\tfor(var i in this.links)\n\t{\n\t\tvar link_info = this.links[i];\n\t\tif( !link_info )\n\t\t\tcontinue;\n\t\tif( link_info._last_time )\n\t\t\tlink_info._last_time = 0;\n\t}\n}\n\n\n/* Called when something visually changed (not the graph!) */\nLGraph.prototype.change = function()\n{\n\tif(LiteGraph.debug)\n\t\tconsole.log(&quot;Graph changed&quot;);\n\tthis.sendActionToCanvas(&quot;setDirty&quot;,[true,true]);\n\tif(this.on_change)\n\t\tthis.on_change(this);\n}\n\nLGraph.prototype.setDirtyCanvas = function(fg,bg)\n{\n\tthis.sendActionToCanvas(&quot;setDirty&quot;,[fg,bg]);\n}\n\n/**\n* Destroys a link\n* @method removeLink\n* @param {Number} link_id\n*/\nLGraph.prototype.removeLink = function(link_id)\n{\n\tvar link = this.links[ link_id ];\n\tif(!link)\n\t\treturn;\n\tvar node = this.getNodeById( link.target_id );\n\tif(node)\n\t\tnode.disconnectInput( link.target_slot );\n}\n\n\n//save and recover app state ***************************************\n/**\n* Creates a Object containing all the info about this graph, it can be serialized\n* @method serialize\n* @return {Object} value of the node\n*/\nLGraph.prototype.serialize = function()\n{\n\tvar nodes_info = [];\n\tfor(var i = 0, l = this._nodes.length; i &lt; l; ++i)\n\t\tnodes_info.push( this._nodes[i].serialize() );\n\n\t//pack link info into a non-verbose format\n\tvar links = [];\n\tfor(var i in this.links) //links is an OBJECT\n\t{\n\t\tvar link = this.links[i];\n\t\tlinks.push([ link.id, link.origin_id, link.origin_slot, link.target_id, link.target_slot, link.type ]);\n\t}\n\n\tvar groups_info = [];\n\tfor(var i = 0; i &lt; this._groups.length; ++i)\n\t\tgroups_info.push( this._groups[i].serialize() );\n\n\tvar data = {\n\t\tlast_node_id: this.last_node_id,\n\t\tlast_link_id: this.last_link_id,\n\t\tnodes: nodes_info,\n\t\tlinks: links, \n\t\tgroups: groups_info,\n\t\tconfig: this.config,\n\t\tversion: LiteGraph.VERSION\n\t};\n\n\treturn data;\n}\n\n\n/**\n* Configure a graph from a JSON string\n* @method configure\n* @param {String} str configure a graph from a JSON string\n* @param {Boolean} returns if there was any error parsing\n*/\nLGraph.prototype.configure = function( data, keep_old )\n{\n\tif(!data)\n\t\treturn;\n\n\tif(!keep_old)\n\t\tthis.clear();\n\n\tvar nodes = data.nodes;\n\n\t//decode links info (they are very verbose)\n\tif(data.links &amp;&amp; data.links.constructor === Array)\n\t{\n\t\tvar links = [];\n\t\tfor(var i = 0; i &lt; data.links.length; ++i)\n\t\t{\n\t\t\tvar link_data = data.links[i];\n\t\t\tvar link = new LLink();\n\t\t\tlink.configure( link_data );\n\t\t\tlinks[ link.id ] = link;\n\t\t}\n\t\tdata.links = links;\n\t}\n\n\t//copy all stored fields\n\tfor (var i in data)\n\t\tthis[i] = data[i];\n\n\tvar error = false;\n\n\t//create nodes\n\tthis._nodes = [];\n\tif(nodes)\n\t{\n\t\tfor(var i = 0, l = nodes.length; i &lt; l; ++i)\n\t\t{\n\t\t\tvar n_info = nodes[i]; //stored info\n\t\t\tvar node = LiteGraph.createNode( n_info.type, n_info.title );\n\t\t\tif(!node)\n\t\t\t{\n\t\t\t\tif(LiteGraph.debug)\n\t\t\t\t\tconsole.log(&quot;Node not found or has errors: &quot; + n_info.type);\n\n\t\t\t\t//in case of error we create a replacement node to avoid losing info\n\t\t\t\tnode = new LGraphNode();\n\t\t\t\tnode.last_serialization = n_info;\n\t\t\t\tnode.has_errors = true;\n\t\t\t\terror = true;\n\t\t\t\t//continue;\n\t\t\t}\n\n\t\t\tnode.id = n_info.id; //id it or it will create a new id\n\t\t\tthis.add(node, true); //add before configure, otherwise configure cannot create links\n\t\t}\n\n\t\t//configure nodes afterwards so they can reach each other\n\t\tfor(var i = 0, l = nodes.length; i &lt; l; ++i)\n\t\t{\n\t\t\tvar n_info = nodes[i];\n\t\t\tvar node = this.getNodeById( n_info.id );\n\t\t\tif(node)\n\t\t\t\tnode.configure( n_info );\n\t\t}\n\t}\n\n\t//groups\n\tthis._groups.length = 0;\n\tif( data.groups )\n\tfor(var i = 0; i &lt; data.groups.length; ++i )\n\t{\n\t\tvar group = new LiteGraph.LGraphGroup();\n\t\tgroup.configure( data.groups[i] );\n\t\tthis.add( group );\n\t}\n\n\tthis.updateExecutionOrder();\n\tthis._version++;\n\tthis.setDirtyCanvas(true,true);\n\treturn error;\n}\n\nLGraph.prototype.load = function(url)\n{\n\tvar that = this;\n\tvar req = new XMLHttpRequest();\n\treq.open(&#x27;GET&#x27;, url, true);\n\treq.send(null);\n\treq.onload = function (oEvent) {\n\t\tif(req.status !== 200)\n\t\t{\n\t\t\tconsole.error(&quot;Error loading graph:&quot;,req.status,req.response);\n\t\t\treturn;\n\t\t}\n\t\tvar data = JSON.parse( req.response );\n\t\tthat.configure(data);\n\t}\n\treq.onerror = function(err)\n\t{\n\t\tconsole.error(&quot;Error loading graph:&quot;,err);\n\t}\n}\n\nLGraph.prototype.onNodeTrace = function(node, msg, color)\n{\n\t//TODO\n}\n\n//this is the class in charge of storing link information\nfunction LLink( id, type, origin_id, origin_slot, target_id, target_slot )\n{\n\tthis.id = id;\n\tthis.type = type;\n\tthis.origin_id = origin_id;\n\tthis.origin_slot = origin_slot;\n\tthis.target_id = target_id;\n\tthis.target_slot = target_slot;\n\n\tthis._data = null;\n\tthis._pos = new Float32Array(2); //center\n}\n\nLLink.prototype.configure = function(o)\n{\n\tif(o.constructor === Array)\n\t{\n\t\tthis.id = o[0];\n\t\tthis.origin_id = o[1];\n\t\tthis.origin_slot = o[2];\n\t\tthis.target_id = o[3];\n\t\tthis.target_slot = o[4];\n\t\tthis.type = o[5];\n\t}\n\telse\n\t{\n\t\tthis.id = o.id;\n\t\tthis.type = o.type;\n\t\tthis.origin_id = o.origin_id;\n\t\tthis.origin_slot = o.origin_slot;\n\t\tthis.target_id = o.target_id;\n\t\tthis.target_slot = o.target_slot;\n\t}\n}\n\nLLink.prototype.serialize = function()\n{\n\treturn [ this.id, this.type, this.origin_id, this.origin_slot, this.target_id, this.target_slot ];\n}\n\nLiteGraph.LLink = LLink;\n\n// *************************************************************\n//   Node CLASS                                          *******\n// *************************************************************\n\n/*\n\ttitle: string\n\tpos: [x,y]\n\tsize: [x,y]\n\n\tinput|output: every connection\n\t\t+  { name:string, type:string, pos: [x,y]=Optional, direction: &quot;input&quot;|&quot;output&quot;, links: Array });\n\n\tgeneral properties:\n\t\t+ clip_area: if you render outside the node, it will be cliped\n\t\t+ unsafe_execution: not allowed for safe execution\n\t\t+ skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected\n\t\t+ resizable: if set to false it wont be resizable with the mouse\n\t\t+ horizontal: slots are distributed horizontally\n\t\t+ widgets_up: widgets start from the top of the node\n\t\n\tflags object:\n\t\t+ collapsed: if it is collapsed\n\n\tsupported callbacks:\n\t\t+ onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading)\n\t\t+ onRemoved: when removed from graph\n\t\t+ onStart:\twhen the graph starts playing\n\t\t+ onStop:\twhen the graph stops playing\n\t\t+ onDrawForeground: render the inside widgets inside the node\n\t\t+ onDrawBackground: render the background area inside the node (only in edit mode)\n\t\t+ onMouseDown\n\t\t+ onMouseMove\n\t\t+ onMouseUp\n\t\t+ onMouseEnter\n\t\t+ onMouseLeave\n\t\t+ onExecute: execute the node\n\t\t+ onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour)\n\t\t+ onGetInputs: returns an array of possible inputs\n\t\t+ onGetOutputs: returns an array of possible outputs\n\t\t+ onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h])\n\t\t+ onDblClick: double clicked in the node\n\t\t+ onInputDblClick: input slot double clicked (can be used to automatically create a node connected)\n\t\t+ onOutputDblClick: output slot double clicked (can be used to automatically create a node connected)\n\t\t+ onConfigure: called after the node has been configured\n\t\t+ onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data)\n\t\t+ onSelected\n\t\t+ onDeselected\n\t\t+ onDropItem : DOM item dropped over the node\n\t\t+ onDropFile : file dropped over the node\n\t\t+ onConnectInput : if returns false the incoming connection will be canceled\n\t\t+ onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info )\n\t\t+ onAction: action slot triggered\n\t\t+ getExtraMenuOptions: to add option to context menu\n*/\n\n/**\n* Base Class for all the node type classes\n* @class LGraphNode\n* @param {String} name a name for the node\n*/\n\nfunction LGraphNode(title)\n{\n\tthis._ctor(title);\n}\n\nglobal.LGraphNode = LiteGraph.LGraphNode = LGraphNode;\n\nLGraphNode.prototype._ctor = function( title )\n{\n\tthis.title = title || &quot;Unnamed&quot;;\n\tthis.size = [LiteGraph.NODE_WIDTH,60];\n\tthis.graph = null;\n\n\tthis._pos = new Float32Array(10,10);\n\n\tObject.defineProperty( this, &quot;pos&quot;, {\n\t\tset: function(v)\n\t\t{\n\t\t\tif(!v || v.length &lt; 2)\n\t\t\t\treturn;\n\t\t\tthis._pos[0] = v[0];\n\t\t\tthis._pos[1] = v[1];\n\t\t},\n\t\tget: function()\n\t\t{\n\t\t\treturn this._pos;\n\t\t},\n\t\tenumerable: true\n\t});\n\n\tthis.id = -1; //not know till not added\n\tthis.type = null;\n\n\t//inputs available: array of inputs\n\tthis.inputs = [];\n\tthis.outputs = [];\n\tthis.connections = [];\n\n\t//local data\n\tthis.properties = {}; //for the values\n\tthis.properties_info = []; //for the info\n\n\tthis.flags = {};\n}\n\n/**\n* configure a node from an object containing the serialized info\n* @method configure\n*/\nLGraphNode.prototype.configure = function(info)\n{\n\tif(this.graph)\n\t\tthis.graph._version++;\n\n\tfor (var j in info)\n\t{\n\t\tif(j == &quot;properties&quot;)\n\t\t{\n\t\t\t//i dont want to clone properties, I want to reuse the old container\n\t\t\tfor(var k in info.properties)\n\t\t\t{\n\t\t\t\tthis.properties[k] = info.properties[k];\n\t\t\t\tif(this.onPropertyChanged)\n\t\t\t\t\tthis.onPropertyChanged(k,info.properties[k]);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif(info[j] == null)\n\t\t\tcontinue;\n\n\t\telse if (typeof(info[j]) == &#x27;object&#x27;) //object\n\t\t{\n\t\t\tif(this[j] &amp;&amp; this[j].configure)\n\t\t\t\tthis[j].configure( info[j] );\n\t\t\telse\n\t\t\t\tthis[j] = LiteGraph.cloneObject(info[j], this[j]);\n\t\t}\n\t\telse //value\n\t\t\tthis[j] = info[j];\n\t}\n\n\tif(!info.title)\n\t\tthis.title = this.constructor.title;\n\n\tif(this.onConnectionsChange)\n\t{\n\t\tif(this.inputs)\n\t\tfor(var i = 0; i &lt; this.inputs.length; ++i)\n\t\t{\n\t\t\tvar input = this.inputs[i];\n\t\t\tvar link_info = this.graph ? this.graph.links[ input.link ] : null;\n\t\t\tthis.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated\n\t\t}\n\n\t\tif(this.outputs)\n\t\tfor(var i = 0; i &lt; this.outputs.length; ++i)\n\t\t{\n\t\t\tvar output = this.outputs[i];\n\t\t\tif(!output.links)\n\t\t\t\tcontinue;\n\t\t\tfor(var j = 0; j &lt; output.links.length; ++j)\n\t\t\t{\n\t\t\t\tvar link_info = this.graph ? this.graph.links[ output.links[j] ] : null;\n\t\t\t\tthis.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated\n\t\t\t}\n\t\t}\n\t}\n\n\tif( this.onConfigure )\n\t\tthis.onConfigure( info );\n}\n\n/**\n* serialize the content\n* @method serialize\n*/\n\nLGraphNode.prototype.serialize = function()\n{\n\t//create serialization object\n\tvar o = {\n\t\tid: this.id,\n\t\ttype: this.type,\n\t\tpos: this.pos,\n\t\tsize: this.size,\n\t\tflags: LiteGraph.cloneObject(this.flags),\n\t\tmode: this.mode\n\t};\n\n\t//special case for when there were errors\n\tif( this.constructor === LGraphNode &amp;&amp; this.last_serialization )\n\t\treturn this.last_serialization;\n\n\tif( this.inputs )\n\t\to.inputs = this.inputs;\n\n\tif( this.outputs )\n\t{\n\t\t//clear outputs last data (because data in connections is never serialized but stored inside the outputs info)\n\t\tfor(var i = 0; i &lt; this.outputs.length; i++)\n\t\t\tdelete this.outputs[i]._data;\n\t\to.outputs = this.outputs;\n\t}\n\n\tif( this.title &amp;&amp; this.title != this.constructor.title )\n\t\to.title = this.title;\n\n\tif( this.properties )\n\t\to.properties = LiteGraph.cloneObject( this.properties );\n\n\tif( !o.type )\n\t\to.type = this.constructor.type;\n\n\tif( this.color )\n\t\to.color = this.color;\n\tif( this.bgcolor )\n\t\to.bgcolor = this.bgcolor;\n\tif( this.boxcolor )\n\t\to.boxcolor = this.boxcolor;\n\tif( this.shape )\n\t\to.shape = this.shape;\n\n\tif(this.onSerialize)\n\t{\n\t\tif( this.onSerialize(o) )\n\t\t\tconsole.warn(&quot;node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter&quot;);\n\t}\n\n\treturn o;\n}\n\n\n/* Creates a clone of this node */\nLGraphNode.prototype.clone = function()\n{\n\tvar node = LiteGraph.createNode(this.type);\n\tif(!node)\n\t\treturn null;\n\n\t//we clone it because serialize returns shared containers\n\tvar data = LiteGraph.cloneObject( this.serialize() );\n\n\t//remove links\n\tif(data.inputs)\n\t\tfor(var i = 0; i &lt; data.inputs.length; ++i)\n\t\t\tdata.inputs[i].link = null;\n\n\tif(data.outputs)\n\t\tfor(var i = 0; i &lt; data.outputs.length; ++i)\n\t\t{\n\t\t\tif(data.outputs[i].links)\n\t\t\t\tdata.outputs[i].links.length = 0;\n\t\t}\n\n\tdelete data[&quot;id&quot;];\n\t//remove links\n\tnode.configure(data);\n\n\treturn node;\n}\n\n\n/**\n* serialize and stringify\n* @method toString\n*/\n\nLGraphNode.prototype.toString = function()\n{\n\treturn JSON.stringify( this.serialize() );\n}\n//LGraphNode.prototype.unserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph\n\n\n/**\n* get the title string\n* @method getTitle\n*/\n\nLGraphNode.prototype.getTitle = function()\n{\n\treturn this.title || this.constructor.title;\n}\n\n\n\n// Execution *************************\n/**\n* sets the output data\n* @method setOutputData\n* @param {number} slot\n* @param {*} data\n*/\nLGraphNode.prototype.setOutputData = function(slot, data)\n{\n\tif(!this.outputs)\n\t\treturn;\n\n\t//this maybe slow and a niche case\n\t//if(slot &amp;&amp; slot.constructor === String)\n\t//\tslot = this.findOutputSlot(slot);\n\n\tif(slot == -1 || slot &gt;= this.outputs.length)\n\t\treturn;\n\n\tvar output_info = this.outputs[slot];\n\tif(!output_info)\n\t\treturn;\n\n\t//store data in the output itself in case we want to debug\n\toutput_info._data = data;\n\n\t//if there are connections, pass the data to the connections\n\tif( this.outputs[slot].links )\n\t{\n\t\tfor(var i = 0; i &lt; this.outputs[slot].links.length; i++)\n\t\t{\n\t\t\tvar link_id = this.outputs[slot].links[i];\n\t\t\tthis.graph.links[ link_id ].data = data;\n\t\t}\n\t}\n}\n\n/**\n* sets the output data type, useful when you want to be able to overwrite the data type\n* @method setOutputDataType\n* @param {number} slot\n* @param {String} datatype\n*/\nLGraphNode.prototype.setOutputDataType = function(slot, type)\n{\n\tif(!this.outputs)\n\t\treturn;\n\tif(slot == -1 || slot &gt;= this.outputs.length)\n\t\treturn;\n\tvar output_info = this.outputs[slot];\n\tif(!output_info)\n\t\treturn;\n\t//store data in the output itself in case we want to debug\n\toutput_info.type = type;\n\n\t//if there are connections, pass the data to the connections\n\tif( this.outputs[slot].links )\n\t{\n\t\tfor(var i = 0; i &lt; this.outputs[slot].links.length; i++)\n\t\t{\n\t\t\tvar link_id = this.outputs[slot].links[i];\n\t\t\tthis.graph.links[ link_id ].type = type;\n\t\t}\n\t}\n}\n\n/**\n* Retrieves the input data (data traveling through the connection) from one slot\n* @method getInputData\n* @param {number} slot\n* @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link\n* @return {*} data or if it is not connected returns undefined\n*/\nLGraphNode.prototype.getInputData = function( slot, force_update )\n{\n\tif(!this.inputs)\n\t\treturn; //undefined;\n\n\tif(slot &gt;= this.inputs.length || this.inputs[slot].link == null)\n\t\treturn;\n\n\tvar link_id = this.inputs[slot].link;\n\tvar link = this.graph.links[ link_id ];\n\tif(!link) //bug: weird case but it happens sometimes\n\t\treturn null;\n\n\tif(!force_update)\n\t\treturn link.data;\n\n\t//special case: used to extract data from the incomming connection before the graph has been executed\n\tvar node = this.graph.getNodeById( link.origin_id );\n\tif(!node)\n\t\treturn link.data;\n\n\tif(node.updateOutputData)\n\t\tnode.updateOutputData( link.origin_slot );\n\telse if(node.onExecute)\n\t\tnode.onExecute();\n\n\treturn link.data;\n}\n\n/**\n* Retrieves the input data type (in case this supports multiple input types)\n* @method getInputDataType\n* @param {number} slot\n* @return {String} datatype in string format\n*/\nLGraphNode.prototype.getInputDataType = function( slot )\n{\n\tif(!this.inputs)\n\t\treturn null; //undefined;\n\n\tif(slot &gt;= this.inputs.length || this.inputs[slot].link == null)\n\t\treturn null;\n\tvar link_id = this.inputs[slot].link;\n\tvar link = this.graph.links[ link_id ];\n\tif(!link) //bug: weird case but it happens sometimes\n\t\treturn null;\n\tvar node = this.graph.getNodeById( link.origin_id );\n\tif(!node)\n\t\treturn link.type;\n\tvar output_info = node.outputs[ link.origin_slot ];\n\tif(output_info)\n\t\treturn output_info.type;\n\treturn null;\n}\n\n/**\n* Retrieves the input data from one slot using its name instead of slot number\n* @method getInputDataByName\n* @param {String} slot_name\n* @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link\n* @return {*} data or if it is not connected returns null\n*/\nLGraphNode.prototype.getInputDataByName = function( slot_name, force_update )\n{\n\tvar slot = this.findInputSlot( slot_name );\n\tif( slot == -1 )\n\t\treturn null;\n\treturn this.getInputData( slot, force_update );\n}\n\n\n/**\n* tells you if there is a connection in one input slot\n* @method isInputConnected\n* @param {number} slot\n* @return {boolean}\n*/\nLGraphNode.prototype.isInputConnected = function(slot)\n{\n\tif(!this.inputs)\n\t\treturn false;\n\treturn (slot &lt; this.inputs.length &amp;&amp; this.inputs[slot].link != null);\n}\n\n/**\n* tells you info about an input connection (which node, type, etc)\n* @method getInputInfo\n* @param {number} slot\n* @return {Object} object or null { link: id, name: string, type: string or 0 }\n*/\nLGraphNode.prototype.getInputInfo = function(slot)\n{\n\tif(!this.inputs)\n\t\treturn null;\n\tif(slot &lt; this.inputs.length)\n\t\treturn this.inputs[slot];\n\treturn null;\n}\n\n/**\n* returns the node connected in the input slot\n* @method getInputNode\n* @param {number} slot\n* @return {LGraphNode} node or null\n*/\nLGraphNode.prototype.getInputNode = function( slot )\n{\n\tif(!this.inputs)\n\t\treturn null;\n\tif(slot &gt;= this.inputs.length)\n\t\treturn null;\n\tvar input = this.inputs[slot];\n\tif(!input || input.link === null)\n\t\treturn null;\n\tvar link_info = this.graph.links[ input.link ];\n\tif(!link_info)\n\t\treturn null;\n\treturn this.graph.getNodeById( link_info.origin_id );\n}\n\n\n/**\n* returns the value of an input with this name, otherwise checks if there is a property with that name\n* @method getInputOrProperty\n* @param {string} name\n* @return {*} value\n*/\nLGraphNode.prototype.getInputOrProperty = function( name )\n{\n\tif(!this.inputs || !this.inputs.length)\n\t\treturn this.properties ? this.properties[name] : null;\n\n\tfor(var i = 0, l = this.inputs.length; i &lt; l; ++i)\n\t{\n\t\tvar input_info = this.inputs[i];\n\t\tif(name == input_info.name &amp;&amp; input_info.link != null)\n\t\t{\n\t\t\tvar link = this.graph.links[ input_info.link ];\n\t\t\tif(link)\n\t\t\t\treturn link.data;\n\t\t}\n\t}\n\treturn this.properties[ name ];\n}\n\n\n\n\n/**\n* tells you the last output data that went in that slot\n* @method getOutputData\n* @param {number} slot\n* @return {Object}  object or null\n*/\nLGraphNode.prototype.getOutputData = function(slot)\n{\n\tif(!this.outputs)\n\t\treturn null;\n\tif(slot &gt;= this.outputs.length)\n\t\treturn null;\n\n\tvar info = this.outputs[slot];\n\treturn info._data;\n}\n\n\n/**\n* tells you info about an output connection (which node, type, etc)\n* @method getOutputInfo\n* @param {number} slot\n* @return {Object}  object or null { name: string, type: string, links: [ ids of links in number ] }\n*/\nLGraphNode.prototype.getOutputInfo = function(slot)\n{\n\tif(!this.outputs)\n\t\treturn null;\n\tif(slot &lt; this.outputs.length)\n\t\treturn this.outputs[slot];\n\treturn null;\n}\n\n\n/**\n* tells you if there is a connection in one output slot\n* @method isOutputConnected\n* @param {number} slot\n* @return {boolean}\n*/\nLGraphNode.prototype.isOutputConnected = function(slot)\n{\n\tif(!this.outputs)\n\t\treturn false;\n\treturn (slot &lt; this.outputs.length &amp;&amp; this.outputs[slot].links &amp;&amp; this.outputs[slot].links.length);\n}\n\n/**\n* tells you if there is any connection in the output slots\n* @method isAnyOutputConnected\n* @return {boolean}\n*/\nLGraphNode.prototype.isAnyOutputConnected = function()\n{\n\tif(!this.outputs)\n\t\treturn false;\n\tfor(var i = 0; i &lt; this.outputs.length; ++i)\n\t\tif( this.outputs[i].links &amp;&amp; this.outputs[i].links.length )\n\t\t\treturn true;\n\treturn false;\n}\n\n\n/**\n* retrieves all the nodes connected to this output slot\n* @method getOutputNodes\n* @param {number} slot\n* @return {array}\n*/\nLGraphNode.prototype.getOutputNodes = function(slot)\n{\n\tif(!this.outputs || this.outputs.length == 0)\n\t\treturn null;\n\n\tif(slot &gt;= this.outputs.length)\n\t\treturn null;\n\n\tvar output = this.outputs[slot];\n\tif(!output.links || output.links.length == 0)\n\t\treturn null;\n\n\tvar r = [];\n\tfor(var i = 0; i &lt; output.links.length; i++)\n\t{\n\t\tvar link_id = output.links[i];\n\t\tvar link = this.graph.links[ link_id ];\n\t\tif(link)\n\t\t{\n\t\t\tvar target_node = this.graph.getNodeById( link.target_id );\n\t\t\tif( target_node )\n\t\t\t\tr.push( target_node );\n\t\t}\n\t}\n\treturn r;\n}\n\n/**\n* Triggers an event in this node, this will trigger any output with the same name\n* @method trigger\n* @param {String} event name ( &quot;on_play&quot;, ... ) if action is equivalent to false then the event is send to all\n* @param {*} param\n*/\nLGraphNode.prototype.trigger = function( action, param )\n{\n\tif( !this.outputs || !this.outputs.length )\n\t\treturn;\n\n\tif(this.graph)\n\t\tthis.graph._last_trigger_time = LiteGraph.getTime();\n\n\tfor(var i = 0; i &lt; this.outputs.length; ++i)\n\t{\n\t\tvar output = this.outputs[ i ];\n\t\tif(!output || output.type !== LiteGraph.EVENT || (action &amp;&amp; output.name != action) )\n\t\t\tcontinue;\n\t\tthis.triggerSlot( i, param );\n\t}\n}\n\n/**\n* Triggers an slot event in this node\n* @method triggerSlot\n* @param {Number} slot the index of the output slot\n* @param {*} param\n* @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot\n*/\nLGraphNode.prototype.triggerSlot = function( slot, param, link_id )\n{\n\tif( !this.outputs )\n\t\treturn;\n\n\tvar output = this.outputs[ slot ];\n\tif( !output )\n\t\treturn;\n\n\tvar links = output.links;\n\tif(!links || !links.length)\n\t\treturn;\n\n\tif(this.graph)\n\t\tthis.graph._last_trigger_time = LiteGraph.getTime();\n\n\t//for every link attached here\n\tfor(var k = 0; k &lt; links.length; ++k)\n\t{\n\t\tvar id = links[k];\n\t\tif( link_id != null &amp;&amp; link_id != id ) //to skip links\n\t\t\tcontinue;\n\t\tvar link_info = this.graph.links[ links[k] ];\n\t\tif(!link_info) //not connected\n\t\t\tcontinue;\n\t\tlink_info._last_time = LiteGraph.getTime();\n\t\tvar node = this.graph.getNodeById( link_info.target_id );\n\t\tif(!node) //node not found?\n\t\t\tcontinue;\n\n\t\t//used to mark events in graph\n\t\tvar target_connection = node.inputs[ link_info.target_slot ];\n\n\t\tif(node.onAction)\n\t\t\tnode.onAction( target_connection.name, param );\n\t\telse if(node.mode === LiteGraph.ON_TRIGGER)\n\t\t{\n\t\t\tif(node.onExecute)\n\t\t\t\tnode.onExecute(param);\n\t\t}\n\t}\n}\n\n/**\n* clears the trigger slot animation\n* @method clearTriggeredSlot\n* @param {Number} slot the index of the output slot\n* @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot\n*/\nLGraphNode.prototype.clearTriggeredSlot = function( slot, link_id )\n{\n\tif( !this.outputs )\n\t\treturn;\n\n\tvar output = this.outputs[ slot ];\n\tif( !output )\n\t\treturn;\n\n\tvar links = output.links;\n\tif(!links || !links.length)\n\t\treturn;\n\n\t//for every link attached here\n\tfor(var k = 0; k &lt; links.length; ++k)\n\t{\n\t\tvar id = links[k];\n\t\tif( link_id != null &amp;&amp; link_id != id ) //to skip links\n\t\t\tcontinue;\n\t\tvar link_info = this.graph.links[ links[k] ];\n\t\tif(!link_info) //not connected\n\t\t\tcontinue;\n\t\tlink_info._last_time = 0;\n\t}\n}\n\n/**\n* add a new property to this node\n* @method addProperty\n* @param {string} name\n* @param {*} default_value\n* @param {string} type string defining the output type (&quot;vec3&quot;,&quot;number&quot;,...)\n* @param {Object} extra_info this can be used to have special properties of the property (like values, etc)\n*/\nLGraphNode.prototype.addProperty = function( name, default_value, type, extra_info )\n{\n\tvar o = { name: name, type: type, default_value: default_value };\n\tif(extra_info)\n\t\tfor(var i in extra_info)\n\t\t\to[i] = extra_info[i];\n\tif(!this.properties_info)\n\t\tthis.properties_info = [];\n\tthis.properties_info.push(o);\n\tif(!this.properties)\n\t\tthis.properties = {};\n\tthis.properties[ name ] = default_value;\n\treturn o;\n}\n\n\n//connections\n\n/**\n* add a new output slot to use in this node\n* @method addOutput\n* @param {string} name\n* @param {string} type string defining the output type (&quot;vec3&quot;,&quot;number&quot;,...)\n* @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc)\n*/\nLGraphNode.prototype.addOutput = function(name,type,extra_info)\n{\n\tvar o = { name: name, type: type, links: null };\n\tif(extra_info)\n\t\tfor(var i in extra_info)\n\t\t\to[i] = extra_info[i];\n\n\tif(!this.outputs)\n\t\tthis.outputs = [];\n\tthis.outputs.push(o);\n\tif(this.onOutputAdded)\n\t\tthis.onOutputAdded(o);\n\tthis.size = this.computeSize();\n\tthis.setDirtyCanvas(true,true);\n\treturn o;\n}\n\n/**\n* add a new output slot to use in this node\n* @method addOutputs\n* @param {Array} array of triplets like [[name,type,extra_info],[...]]\n*/\nLGraphNode.prototype.addOutputs = function(array)\n{\n\tfor(var i = 0; i &lt; array.length; ++i)\n\t{\n\t\tvar info = array[i];\n\t\tvar o = {name:info[0],type:info[1],link:null};\n\t\tif(array[2])\n\t\t\tfor(var j in info[2])\n\t\t\t\to[j] = info[2][j];\n\n\t\tif(!this.outputs)\n\t\t\tthis.outputs = [];\n\t\tthis.outputs.push(o);\n\t\tif(this.onOutputAdded)\n\t\t\tthis.onOutputAdded(o);\n\t}\n\n\tthis.size = this.computeSize();\n\tthis.setDirtyCanvas(true,true);\n}\n\n/**\n* remove an existing output slot\n* @method removeOutput\n* @param {number} slot\n*/\nLGraphNode.prototype.removeOutput = function(slot)\n{\n\tthis.disconnectOutput(slot);\n\tthis.outputs.splice(slot,1);\n\tfor(var i = slot; i &lt; this.outputs.length; ++i)\n\t{\n\t\tif( !this.outputs[i] || !this.outputs[i].links )\n\t\t\tcontinue;\n\t\tvar links = this.outputs[i].links;\n\t\tfor(var j = 0; j &lt; links.length; ++j)\n\t\t{\n\t\t\tvar link = this.graph.links[ links[j] ];\n\t\t\tif(!link)\n\t\t\t\tcontinue;\n\t\t\tlink.origin_slot -= 1;\n\t\t}\n\t}\n\n\tthis.size = this.computeSize();\n\tif(this.onOutputRemoved)\n\t\tthis.onOutputRemoved(slot);\n\tthis.setDirtyCanvas(true,true);\n}\n\n/**\n* add a new input slot to use in this node\n* @method addInput\n* @param {string} name\n* @param {string} type string defining the input type (&quot;vec3&quot;,&quot;number&quot;,...), it its a generic one use 0\n* @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc)\n*/\nLGraphNode.prototype.addInput = function(name,type,extra_info)\n{\n\ttype = type || 0;\n\tvar o = {name:name,type:type,link:null};\n\tif(extra_info)\n\t\tfor(var i in extra_info)\n\t\t\to[i] = extra_info[i];\n\n\tif(!this.inputs)\n\t\tthis.inputs = [];\n\tthis.inputs.push(o);\n\tthis.size = this.computeSize();\n\tif(this.onInputAdded)\n\t\tthis.onInputAdded(o);\n\tthis.setDirtyCanvas(true,true);\n\treturn o;\n}\n\n/**\n* add several new input slots in this node\n* @method addInputs\n* @param {Array} array of triplets like [[name,type,extra_info],[...]]\n*/\nLGraphNode.prototype.addInputs = function(array)\n{\n\tfor(var i = 0; i &lt; array.length; ++i)\n\t{\n\t\tvar info = array[i];\n\t\tvar o = {name:info[0], type:info[1], link:null};\n\t\tif(array[2])\n\t\t\tfor(var j in info[2])\n\t\t\t\to[j] = info[2][j];\n\n\t\tif(!this.inputs)\n\t\t\tthis.inputs = [];\n\t\tthis.inputs.push(o);\n\t\tif(this.onInputAdded)\n\t\t\tthis.onInputAdded(o);\n\t}\n\n\tthis.size = this.computeSize();\n\tthis.setDirtyCanvas(true,true);\n}\n\n/**\n* remove an existing input slot\n* @method removeInput\n* @param {number} slot\n*/\nLGraphNode.prototype.removeInput = function(slot)\n{\n\tthis.disconnectInput(slot);\n\tthis.inputs.splice(slot,1);\n\tfor(var i = slot; i &lt; this.inputs.length; ++i)\n\t{\n\t\tif(!this.inputs[i])\n\t\t\tcontinue;\n\t\tvar link = this.graph.links[ this.inputs[i].link ];\n\t\tif(!link)\n\t\t\tcontinue;\n\t\tlink.target_slot -= 1;\n\t}\n\tthis.size = this.computeSize();\n\tif(this.onInputRemoved)\n\t\tthis.onInputRemoved(slot);\n\tthis.setDirtyCanvas(true,true);\n}\n\n/**\n* add an special connection to this node (used for special kinds of graphs)\n* @method addConnection\n* @param {string} name\n* @param {string} type string defining the input type (&quot;vec3&quot;,&quot;number&quot;,...)\n* @param {[x,y]} pos position of the connection inside the node\n* @param {string} direction if is input or output\n*/\nLGraphNode.prototype.addConnection = function(name,type,pos,direction)\n{\n\tvar o = {\n\t\tname: name,\n\t\ttype: type,\n\t\tpos: pos,\n\t\tdirection: direction,\n\t\tlinks: null\n\t};\n\tthis.connections.push( o );\n\treturn o;\n}\n\n/**\n* computes the size of a node according to its inputs and output slots\n* @method computeSize\n* @param {number} minHeight\n* @return {number} the total size\n*/\nLGraphNode.prototype.computeSize = function( minHeight, out )\n{\n\tif( this.constructor.size )\n\t\treturn this.constructor.size.concat();\n\n\tvar rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1);\n\tvar size = out || new Float32Array([0,0]);\n\trows = Math.max(rows, 1);\n\tvar font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size\n\tsize[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT;\n\tif( this.widgets &amp;&amp; this.widgets.length )\n\t\tsize[1] += this.widgets.length * (LiteGraph.NODE_WIDGET_HEIGHT + 4) + 8;\n\n\tvar font_size = font_size;\n\tvar title_width = compute_text_size( this.title );\n\tvar input_width = 0;\n\tvar output_width = 0;\n\n\tif(this.inputs)\n\t\tfor(var i = 0, l = this.inputs.length; i &lt; l; ++i)\n\t\t{\n\t\t\tvar input = this.inputs[i];\n\t\t\tvar text = input.label || input.name || &quot;&quot;;\n\t\t\tvar text_width = compute_text_size( text );\n\t\t\tif(input_width &lt; text_width)\n\t\t\t\tinput_width = text_width;\n\t\t}\n\n\tif(this.outputs)\n\t\tfor(var i = 0, l = this.outputs.length; i &lt; l; ++i)\n\t\t{\n\t\t\tvar output = this.outputs[i];\n\t\t\tvar text = output.label || output.name || &quot;&quot;;\n\t\t\tvar text_width = compute_text_size( text );\n\t\t\tif(output_width &lt; text_width)\n\t\t\t\toutput_width = text_width;\n\t\t}\n\n\tsize[0] = Math.max( input_width + output_width + 10, title_width );\n\tsize[0] = Math.max( size[0], LiteGraph.NODE_WIDTH );\n\tif(this.widgets &amp;&amp; this.widgets.length)\n\t\tsize[0] = Math.max( size[0], LiteGraph.NODE_WIDTH * 1.5 );\n\n\tif(this.onResize)\n\t\tthis.onResize(size);\n\n\tfunction compute_text_size( text )\n\t{\n\t\tif(!text)\n\t\t\treturn 0;\n\t\treturn font_size * text.length * 0.6;\n\t}\n\n\tif(this.constructor.min_height &amp;&amp; size[1] &lt; this.constructor.min_height)\n\t\tsize[1] = this.constructor.min_height;\n\n\tsize[1] += 6; //margin\n\n\treturn size;\n}\n\n/**\n* Allows to pass \n* \n* @method addWidget\n* @return {Object} the created widget\n*/\nLGraphNode.prototype.addWidget = function( type, name, value, callback, options )\n{\n\tif(!this.widgets)\n\t\tthis.widgets = [];\n\tvar w = {\n\t\ttype: type.toLowerCase(),\n\t\tname: name,\n\t\tvalue: value,\n\t\tcallback: callback,\n\t\toptions: options || {}\n\t};\n\n\tif(w.options.y !== undefined )\n\t\tw.y = w.options.y;\n\n\tif( !callback )\n\t\tconsole.warn(&quot;LiteGraph addWidget(&#x27;button&#x27;,...) without a callback&quot;);\n\tif( type == &quot;combo&quot; &amp;&amp; !w.options.values )\n\t\tthrow(&quot;LiteGraph addWidget(&#x27;combo&#x27;,...) requires to pass values in options: { values:[&#x27;red&#x27;,&#x27;blue&#x27;] }&quot;);\n\tthis.widgets.push(w);\n\treturn w;\n}\n\nLGraphNode.prototype.addCustomWidget = function( custom_widget )\n{\n\tif(!this.widgets)\n\t\tthis.widgets = [];\n\tthis.widgets.push(custom_widget);\n\treturn custom_widget;\n}\n\n\n/**\n* returns the bounding of the object, used for rendering purposes\n* bounding is: [topleft_cornerx, topleft_cornery, width, height]\n* @method getBounding\n* @return {Float32Array[4]} the total size\n*/\nLGraphNode.prototype.getBounding = function( out )\n{\n\tout = out || new Float32Array(4);\n\tout[0] = this.pos[0] - 4;\n\tout[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n\tout[2] = this.size[0] + 4;\n\tout[3] = this.size[1] + LiteGraph.NODE_TITLE_HEIGHT;\n\n\tif( this.onBounding )\n\t\tthis.onBounding( out );\n\treturn out;\n}\n\n/**\n* checks if a point is inside the shape of a node\n* @method isPointInside\n* @param {number} x\n* @param {number} y\n* @return {boolean}\n*/\nLGraphNode.prototype.isPointInside = function( x, y, margin, skip_title )\n{\n\tmargin = margin || 0;\n\n\tvar margin_top = this.graph &amp;&amp; this.graph.isLive() ? 0 : 20;\n\tif(skip_title)\n\t\tmargin_top = 0;\n\tif(this.flags &amp;&amp; this.flags.collapsed)\n\t{\n\t\t//if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) &lt; LiteGraph.NODE_COLLAPSED_RADIUS)\n\t\tif( isInsideRectangle( x, y, this.pos[0] - margin, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, (this._collapsed_width||LiteGraph.NODE_COLLAPSED_WIDTH) + 2 * margin, LiteGraph.NODE_TITLE_HEIGHT + 2 * margin ) )\n\t\t\treturn true;\n\t}\n\telse if ( (this.pos[0] - 4 - margin) &lt; x &amp;&amp; (this.pos[0] + this.size[0] + 4 + margin) &gt; x\n\t\t&amp;&amp; (this.pos[1] - margin_top - margin) &lt; y &amp;&amp; (this.pos[1] + this.size[1] + margin) &gt; y)\n\t\treturn true;\n\treturn false;\n}\n\n/**\n* checks if a point is inside a node slot, and returns info about which slot\n* @method getSlotInPosition\n* @param {number} x\n* @param {number} y\n* @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }\n*/\nLGraphNode.prototype.getSlotInPosition = function( x, y )\n{\n\t//search for inputs\n\tvar link_pos = new Float32Array(2);\n\tif(this.inputs)\n\t\tfor(var i = 0, l = this.inputs.length; i &lt; l; ++i)\n\t\t{\n\t\t\tvar input = this.inputs[i];\n\t\t\tthis.getConnectionPos( true,i, link_pos );\n\t\t\tif( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) )\n\t\t\t\treturn { input: input, slot: i, link_pos: link_pos, locked: input.locked };\n\t\t}\n\n\tif(this.outputs)\n\t\tfor(var i = 0, l = this.outputs.length; i &lt; l; ++i)\n\t\t{\n\t\t\tvar output = this.outputs[i];\n\t\t\tthis.getConnectionPos(false,i,link_pos);\n\t\t\tif( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) )\n\t\t\t\treturn { output: output, slot: i, link_pos: link_pos, locked: output.locked };\n\t\t}\n\n\treturn null;\n}\n\n/**\n* returns the input slot with a given name (used for dynamic slots), -1 if not found\n* @method findInputSlot\n* @param {string} name the name of the slot\n* @return {number} the slot (-1 if not found)\n*/\nLGraphNode.prototype.findInputSlot = function(name)\n{\n\tif(!this.inputs)\n\t\treturn -1;\n\tfor(var i = 0, l = this.inputs.length; i &lt; l; ++i)\n\t\tif(name == this.inputs[i].name)\n\t\t\treturn i;\n\treturn -1;\n}\n\n/**\n* returns the output slot with a given name (used for dynamic slots), -1 if not found\n* @method findOutputSlot\n* @param {string} name the name of the slot\n* @return {number} the slot (-1 if not found)\n*/\nLGraphNode.prototype.findOutputSlot = function(name)\n{\n\tif(!this.outputs) return -1;\n\tfor(var i = 0, l = this.outputs.length; i &lt; l; ++i)\n\t\tif(name == this.outputs[i].name)\n\t\t\treturn i;\n\treturn -1;\n}\n\n/**\n* connect this node output to the input of another node\n* @method connect\n* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n* @param {LGraphNode} node the target node\n* @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)\n* @return {Object} the link_info is created, otherwise null\n*/\nLGraphNode.prototype.connect = function( slot, target_node, target_slot )\n{\n\ttarget_slot = target_slot || 0;\n\n\tif(!this.graph) //could be connected before adding it to a graph\n\t{\n\t\tconsole.log(&quot;Connect: Error, node doesnt belong to any graph. Nodes must be added first to a graph before connecting them.&quot;); //due to link ids being associated with graphs\n\t\treturn null;\n\t}\n\n\n\t//seek for the output slot\n\tif( slot.constructor === String )\n\t{\n\t\tslot = this.findOutputSlot(slot);\n\t\tif(slot == -1)\n\t\t{\n\t\t\tif(LiteGraph.debug)\n\t\t\t\tconsole.log(&quot;Connect: Error, no slot of name &quot; + slot);\n\t\t\treturn null;\n\t\t}\n\t}\n\telse if(!this.outputs || slot &gt;= this.outputs.length)\n\t{\n\t\tif(LiteGraph.debug)\n\t\t\tconsole.log(&quot;Connect: Error, slot number not found&quot;);\n\t\treturn null;\n\t}\n\n\tif(target_node &amp;&amp; target_node.constructor === Number)\n\t\ttarget_node = this.graph.getNodeById( target_node );\n\tif(!target_node)\n\t\tthrow(&quot;target node is null&quot;);\n\n\t//avoid loopback\n\tif(target_node == this)\n\t\treturn null;\n\n\t//you can specify the slot by name\n\tif(target_slot.constructor === String)\n\t{\n\t\ttarget_slot = target_node.findInputSlot( target_slot );\n\t\tif(target_slot == -1)\n\t\t{\n\t\t\tif(LiteGraph.debug)\n\t\t\t\tconsole.log(&quot;Connect: Error, no slot of name &quot; + target_slot);\n\t\t\treturn null;\n\t\t}\n\t}\n\telse if( target_slot === LiteGraph.EVENT )\n\t{\n\t\t//search for first slot with event?\n\t\t/*\n\t\t//create input for trigger\n\t\tvar input = target_node.addInput(&quot;onTrigger&quot;, LiteGraph.EVENT );\n\t\ttarget_slot = target_node.inputs.length - 1; //last one is the one created\n\t\ttarget_node.mode = LiteGraph.ON_TRIGGER;\n\t\t*/\n\t\treturn null;\n\t}\n\telse if( !target_node.inputs || target_slot &gt;= target_node.inputs.length )\n\t{\n\t\tif(LiteGraph.debug)\n\t\t\tconsole.log(&quot;Connect: Error, slot number not found&quot;);\n\t\treturn null;\n\t}\n\n\t//if there is something already plugged there, disconnect\n\tif(target_node.inputs[ target_slot ].link != null )\n\t\ttarget_node.disconnectInput( target_slot );\n\n\t//why here??\n\t//this.setDirtyCanvas(false,true);\n\t//this.graph.connectionChange( this );\n\n\tvar output = this.outputs[slot];\n\n\t//allows nodes to block connection\n\tif(target_node.onConnectInput)\n\t\tif( target_node.onConnectInput( target_slot, output.type, output ) === false)\n\t\t\treturn null;\n\n\tvar input = target_node.inputs[target_slot];\n\tvar link_info = null;\n\n\tif( LiteGraph.isValidConnection( output.type, input.type ) )\n\t{\n\t\tlink_info = new LLink( this.graph.last_link_id++, input.type, this.id, slot, target_node.id, target_slot );\n\n\t\t//add to graph links list\n\t\tthis.graph.links[ link_info.id ] = link_info;\n\n\t\t//connect in output\n\t\tif( output.links == null )\n\t\t\toutput.links = [];\n\t\toutput.links.push( link_info.id );\n\t\t//connect in input\n\t\ttarget_node.inputs[target_slot].link = link_info.id;\n\t\tif(this.graph)\n\t\t\tthis.graph._version++;\n\t\tif(this.onConnectionsChange)\n\t\t\tthis.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info, output ); //link_info has been created now, so its updated\n\t\tif(target_node.onConnectionsChange)\n\t\t\ttarget_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info, input );\n\t\tif( this.graph &amp;&amp; this.graph.onNodeConnectionChange )\n\t\t{\n\t\t\tthis.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, target_slot, this, slot );\n\t\t\tthis.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot, target_node, target_slot );\n\t\t}\n\t}\n\n\tthis.setDirtyCanvas(false,true);\n\tthis.graph.connectionChange( this, link_info );\n\n\treturn link_info;\n}\n\n/**\n* disconnect one output to an specific node\n* @method disconnectOutput\n* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n* @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]\n* @return {boolean} if it was disconnected succesfully\n*/\nLGraphNode.prototype.disconnectOutput = function( slot, target_node )\n{\n\tif( slot.constructor === String )\n\t{\n\t\tslot = this.findOutputSlot(slot);\n\t\tif(slot == -1)\n\t\t{\n\t\t\tif(LiteGraph.debug)\n\t\t\t\tconsole.log(&quot;Connect: Error, no slot of name &quot; + slot);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse if(!this.outputs || slot &gt;= this.outputs.length)\n\t{\n\t\tif(LiteGraph.debug)\n\t\t\tconsole.log(&quot;Connect: Error, slot number not found&quot;);\n\t\treturn false;\n\t}\n\n\t//get output slot\n\tvar output = this.outputs[slot];\n\tif(!output || !output.links || output.links.length == 0)\n\t\treturn false;\n\n\t//one of the output links in this slot\n\tif(target_node)\n\t{\n\t\tif(target_node.constructor === Number)\n\t\t\ttarget_node = this.graph.getNodeById( target_node );\n\t\tif(!target_node)\n\t\t\tthrow(&quot;Target Node not found&quot;);\n\n\t\tfor(var i = 0, l = output.links.length; i &lt; l; i++)\n\t\t{\n\t\t\tvar link_id = output.links[i];\n\t\t\tvar link_info = this.graph.links[ link_id ];\n\n\t\t\t//is the link we are searching for...\n\t\t\tif( link_info.target_id == target_node.id )\n\t\t\t{\n\t\t\t\toutput.links.splice(i,1); //remove here\n\t\t\t\tvar input = target_node.inputs[ link_info.target_slot ];\n\t\t\t\tinput.link = null; //remove there\n\t\t\t\tdelete this.graph.links[ link_id ]; //remove the link from the links pool\n\t\t\t\tif(this.graph)\n\t\t\t\t\tthis.graph._version++;\n\t\t\t\tif(target_node.onConnectionsChange)\n\t\t\t\t\ttarget_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok\n\t\t\t\tif(this.onConnectionsChange)\n\t\t\t\t\tthis.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output );\n\t\t\t\tif( this.graph &amp;&amp; this.graph.onNodeConnectionChange )\n\t\t\t\t\tthis.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot );\n\t\t\t\tif( this.graph &amp;&amp; this.graph.onNodeConnectionChange )\n\t\t\t\t{\n\t\t\t\t\tthis.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot );\n\t\t\t\t\tthis.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\telse //all the links in this output slot\n\t{\n\t\tfor(var i = 0, l = output.links.length; i &lt; l; i++)\n\t\t{\n\t\t\tvar link_id = output.links[i];\n\t\t\tvar link_info = this.graph.links[ link_id ];\n\t\t\tif(!link_info) //bug: it happens sometimes\n\t\t\t\tcontinue;\n\n\t\t\tvar target_node = this.graph.getNodeById( link_info.target_id );\n\t\t\tvar input = null;\n\t\t\tif(this.graph)\n\t\t\t\tthis.graph._version++;\n\t\t\tif(target_node)\n\t\t\t{\n\t\t\t\tinput = target_node.inputs[ link_info.target_slot ];\n\t\t\t\tinput.link = null; //remove other side link\n\t\t\t\tif(target_node.onConnectionsChange)\n\t\t\t\t\ttarget_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasnt been modified so its ok\n\t\t\t\tif( this.graph &amp;&amp; this.graph.onNodeConnectionChange )\n\t\t\t\t\tthis.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot );\n\t\t\t}\n\t\t\tdelete this.graph.links[ link_id ]; //remove the link from the links pool\n\t\t\tif(this.onConnectionsChange)\n\t\t\t\tthis.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output );\n\t\t\tif( this.graph &amp;&amp; this.graph.onNodeConnectionChange )\n\t\t\t{\n\t\t\t\tthis.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot );\n\t\t\t\tthis.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot );\n\t\t\t}\n\t\t}\n\t\toutput.links = null;\n\t}\n\n\n\tthis.setDirtyCanvas(false,true);\n\tthis.graph.connectionChange( this );\n\treturn true;\n}\n\n/**\n* disconnect one input\n* @method disconnectInput\n* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n* @return {boolean} if it was disconnected succesfully\n*/\nLGraphNode.prototype.disconnectInput = function( slot )\n{\n\t//seek for the output slot\n\tif( slot.constructor === String )\n\t{\n\t\tslot = this.findInputSlot(slot);\n\t\tif(slot == -1)\n\t\t{\n\t\t\tif(LiteGraph.debug)\n\t\t\t\tconsole.log(&quot;Connect: Error, no slot of name &quot; + slot);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse if(!this.inputs || slot &gt;= this.inputs.length)\n\t{\n\t\tif(LiteGraph.debug)\n\t\t\tconsole.log(&quot;Connect: Error, slot number not found&quot;);\n\t\treturn false;\n\t}\n\n\tvar input = this.inputs[slot];\n\tif(!input)\n\t\treturn false;\n\n\tvar link_id = this.inputs[slot].link;\n\tthis.inputs[slot].link = null;\n\n\t//remove other side\n\tvar link_info = this.graph.links[ link_id ];\n\tif( link_info )\n\t{\n\t\tvar target_node = this.graph.getNodeById( link_info.origin_id );\n\t\tif(!target_node)\n\t\t\treturn false;\n\n\t\tvar output = target_node.outputs[ link_info.origin_slot ];\n\t\tif(!output || !output.links || output.links.length == 0)\n\t\t\treturn false;\n\n\t\t//search in the inputs list for this link\n\t\tfor(var i = 0, l = output.links.length; i &lt; l; i++)\n\t\t{\n\t\t\tif( output.links[i] == link_id )\n\t\t\t{\n\t\t\t\toutput.links.splice(i,1);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tdelete this.graph.links[ link_id ]; //remove from the pool\n\t\tif(this.graph)\n\t\t\tthis.graph._version++;\n\t\tif( this.onConnectionsChange )\n\t\t\tthis.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info, input );\n\t\tif( target_node.onConnectionsChange )\n\t\t\ttarget_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info, output );\n\t\tif( this.graph &amp;&amp; this.graph.onNodeConnectionChange )\n\t\t{\n\t\t\tthis.graph.onNodeConnectionChange( LiteGraph.OUTPUT, target_node, i );\n\t\t\tthis.graph.onNodeConnectionChange( LiteGraph.INPUT, this, slot );\n\t\t}\n\t}\n\n\tthis.setDirtyCanvas(false,true);\n\tthis.graph.connectionChange( this );\n\treturn true;\n}\n\n/**\n* returns the center of a connection point in canvas coords\n* @method getConnectionPos\n* @param {boolean} is_input true if if a input slot, false if it is an output\n* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n* @param {vec2} out [optional] a place to store the output, to free garbage\n* @return {[x,y]} the position\n**/\nLGraphNode.prototype.getConnectionPos = function( is_input, slot_number, out )\n{\n\tout = out || new Float32Array(2);\n\tvar num_slots = 0;\n\tif( is_input &amp;&amp; this.inputs )\n\t\tnum_slots = this.inputs.length;\n\tif( !is_input &amp;&amp; this.outputs )\n\t\tnum_slots = this.outputs.length;\n\n\tvar offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n\n\tif(this.flags.collapsed)\n\t{\n\t\tvar w = (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH);\n\t\tif( this.horizontal )\n\t\t{\n\t\t\tout[0] = this.pos[0] + w * 0.5; \n\t\t\tif(is_input)\n\t\t\t\tout[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n\t\t\telse\n\t\t\t\tout[1] = this.pos[1];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif(is_input)\n\t\t\t\tout[0] = this.pos[0];\n\t\t\telse\n\t\t\t\tout[0] = this.pos[0] + w;\n\t\t\tout[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n\t\t}\n\t\treturn out;\n\t}\n\n\t//weird feature that never got finished\n\tif(is_input &amp;&amp; slot_number == -1)\n\t{\n\t\tout[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n\t\tout[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n\t\treturn out;\n\t}\n\n\t//hardcoded pos\n\tif(is_input &amp;&amp; num_slots &gt; slot_number &amp;&amp; this.inputs[ slot_number ].pos)\n\t{\n\t\tout[0] = this.pos[0] + this.inputs[slot_number].pos[0];\n\t\tout[1] = this.pos[1] + this.inputs[slot_number].pos[1];\n\t\treturn out;\n\t}\n\telse if(!is_input &amp;&amp; num_slots &gt; slot_number &amp;&amp; this.outputs[ slot_number ].pos)\n\t{\n\t\tout[0] = this.pos[0] + this.outputs[slot_number].pos[0];\n\t\tout[1] = this.pos[1] + this.outputs[slot_number].pos[1];\n\t\treturn out;\n\t}\n\n\t//horizontal distributed slots\n\tif(this.horizontal)\n\t{\n\t\tout[0] = this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots);\n\t\tif(is_input)\n\t\t\tout[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n\t\telse\n\t\t\tout[1] = this.pos[1] + this.size[1];\n\t\treturn out;\n\t}\n\t\n\t//default vertical slots\n\tif(is_input)\n\t\tout[0] = this.pos[0] + offset;\n\telse\n\t\tout[0] = this.pos[0] + this.size[0] + 1 - offset;\n\tout[1] = this.pos[1] + (slot_number + 0.7 ) * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0);\n\treturn out;\n}\n\n/* Force align to grid */\nLGraphNode.prototype.alignToGrid = function()\n{\n\tthis.pos[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE);\n\tthis.pos[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE);\n}\n\n\n/* Console output */\nLGraphNode.prototype.trace = function(msg)\n{\n\tif(!this.console)\n\t\tthis.console = [];\n\tthis.console.push(msg);\n\tif(this.console.length &gt; LGraphNode.MAX_CONSOLE)\n\t\tthis.console.shift();\n\n\tthis.graph.onNodeTrace(this,msg);\n}\n\n/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */\nLGraphNode.prototype.setDirtyCanvas = function(dirty_foreground, dirty_background)\n{\n\tif(!this.graph)\n\t\treturn;\n\tthis.graph.sendActionToCanvas(&quot;setDirty&quot;,[dirty_foreground, dirty_background]);\n}\n\nLGraphNode.prototype.loadImage = function(url)\n{\n\tvar img = new Image();\n\timg.src = LiteGraph.node_images_path + url;\n\timg.ready = false;\n\n\tvar that = this;\n\timg.onload = function() {\n\t\tthis.ready = true;\n\t\tthat.setDirtyCanvas(true);\n\t}\n\treturn img;\n}\n\n//safe LGraphNode action execution (not sure if safe)\n/*\nLGraphNode.prototype.executeAction = function(action)\n{\n\tif(action == &quot;&quot;) return false;\n\n\tif( action.indexOf(&quot;;&quot;) != -1 || action.indexOf(&quot;}&quot;) != -1)\n\t{\n\t\tthis.trace(&quot;Error: Action contains unsafe characters&quot;);\n\t\treturn false;\n\t}\n\n\tvar tokens = action.split(&quot;(&quot;);\n\tvar func_name = tokens[0];\n\tif( typeof(this[func_name]) != &quot;function&quot;)\n\t{\n\t\tthis.trace(&quot;Error: Action not found on node: &quot; + func_name);\n\t\treturn false;\n\t}\n\n\tvar code = action;\n\n\ttry\n\t{\n\t\tvar _foo = eval;\n\t\teval = null;\n\t\t(new Function(&quot;with(this) { &quot; + code + &quot;}&quot;)).call(this);\n\t\teval = _foo;\n\t}\n\tcatch (err)\n\t{\n\t\tthis.trace(&quot;Error executing action {&quot; + action + &quot;} :&quot; + err);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n*/\n\n/* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */\nLGraphNode.prototype.captureInput = function(v)\n{\n\tif(!this.graph || !this.graph.list_of_graphcanvas)\n\t\treturn;\n\n\tvar list = this.graph.list_of_graphcanvas;\n\n\tfor(var i = 0; i &lt; list.length; ++i)\n\t{\n\t\tvar c = list[i];\n\t\t//releasing somebody elses capture?!\n\t\tif(!v &amp;&amp; c.node_capturing_input != this)\n\t\t\tcontinue;\n\n\t\t//change\n\t\tc.node_capturing_input = v ? this : null;\n\t}\n}\n\n/**\n* Collapse the node to make it smaller on the canvas\n* @method collapse\n**/\nLGraphNode.prototype.collapse = function( force )\n{\n\tthis.graph._version++;\n\tif(this.constructor.collapsable === false &amp;&amp; !force)\n\t\treturn;\n\tif(!this.flags.collapsed)\n\t\tthis.flags.collapsed = true;\n\telse\n\t\tthis.flags.collapsed = false;\n\tthis.setDirtyCanvas(true,true);\n}\n\n/**\n* Forces the node to do not move or realign on Z\n* @method pin\n**/\n\nLGraphNode.prototype.pin = function(v)\n{\n\tthis.graph._version++;\n\tif(v === undefined)\n\t\tthis.flags.pinned = !this.flags.pinned;\n\telse\n\t\tthis.flags.pinned = v;\n}\n\nLGraphNode.prototype.localToScreen = function(x,y, graphcanvas)\n{\n\treturn [(x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0],\n\t\t(y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]];\n}\n\n\n\n\nfunction LGraphGroup( title )\n{\n\tthis._ctor( title );\n}\n\nglobal.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup;\n\nLGraphGroup.prototype._ctor = function( title )\n{\n\tthis.title = title || &quot;Group&quot;;\n\tthis.font_size = 24;\n\tthis.color = LGraphCanvas.node_colors.pale_blue ? LGraphCanvas.node_colors.pale_blue.groupcolor : &quot;#AAA&quot;;\n\tthis._bounding = new Float32Array([10,10,140,80]);\n\tthis._pos = this._bounding.subarray(0,2);\n\tthis._size = this._bounding.subarray(2,4);\n\tthis._nodes = [];\n\tthis.graph = null;\n\n\tObject.defineProperty( this, &quot;pos&quot;, {\n\t\tset: function(v)\n\t\t{\n\t\t\tif(!v || v.length &lt; 2)\n\t\t\t\treturn;\n\t\t\tthis._pos[0] = v[0];\n\t\t\tthis._pos[1] = v[1];\n\t\t},\n\t\tget: function()\n\t\t{\n\t\t\treturn this._pos;\n\t\t},\n\t\tenumerable: true\n\t});\n\n\tObject.defineProperty( this, &quot;size&quot;, {\n\t\tset: function(v)\n\t\t{\n\t\t\tif(!v || v.length &lt; 2)\n\t\t\t\treturn;\n\t\t\tthis._size[0] = Math.max(140,v[0]);\n\t\t\tthis._size[1] = Math.max(80,v[1]);\n\t\t},\n\t\tget: function()\n\t\t{\n\t\t\treturn this._size;\n\t\t},\n\t\tenumerable: true\n\t});\n}\n\nLGraphGroup.prototype.configure = function(o)\n{\n\tthis.title = o.title;\n\tthis._bounding.set( o.bounding );\n\tthis.color = o.color;\n\tthis.font = o.font;\n}\n\nLGraphGroup.prototype.serialize = function()\n{\n\tvar b = this._bounding;\n\treturn {\n\t\ttitle: this.title,\n\t\tbounding: [ Math.round(b[0]), Math.round(b[1]), Math.round(b[2]), Math.round(b[3]) ],\n\t\tcolor: this.color,\n\t\tfont: this.font\n\t};\n}\n\nLGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes)\n{\n\tthis._pos[0] += deltax;\n\tthis._pos[1] += deltay;\n\tif(ignore_nodes)\n\t\treturn;\n\tfor(var i = 0; i &lt; this._nodes.length; ++i)\n\t{\n\t\tvar node = this._nodes[i];\n\t\tnode.pos[0] += deltax;\n\t\tnode.pos[1] += deltay;\n\t}\n}\n\nLGraphGroup.prototype.recomputeInsideNodes = function()\n{\n\tthis._nodes.length = 0;\n\tvar nodes = this.graph._nodes;\n\tvar node_bounding = new Float32Array(4);\n\n\tfor(var i = 0; i &lt; nodes.length; ++i)\n\t{\n\t\tvar node = nodes[i];\n\t\tnode.getBounding( node_bounding );\n\t\tif(!overlapBounding( this._bounding, node_bounding ))\n\t\t\tcontinue; //out of the visible area\n\t\tthis._nodes.push( node );\n\t}\n}\n\nLGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside;\nLGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas;\n\n\n\n//****************************************\n\n//Scale and Offset\nfunction DragAndScale( element, skip_events )\n{\n\tthis.offset = new Float32Array([0,0]);\n\tthis.scale = 1;\n\tthis.max_scale = 10;\n\tthis.min_scale = 0.1;\n\tthis.onredraw = null;\n\tthis.enabled = true;\n\tthis.last_mouse = [0,0];\n\tthis.element = null;\n\tthis.visible_area = new Float32Array(4);\n\n\tif(element)\n\t{\n\t\tthis.element = element;\n\t\tif(!skip_events)\n\t\t\tthis.bindEvents( element );\n\t}\n}\n\nLiteGraph.DragAndScale = DragAndScale;\n\nDragAndScale.prototype.bindEvents = function( element )\n{\n\tthis.last_mouse = new Float32Array(2);\n\n\tthis._binded_mouse_callback = this.onMouse.bind(this);\n\n\telement.addEventListener(&quot;mousedown&quot;, this._binded_mouse_callback );\n\telement.addEventListener(&quot;mousemove&quot;, this._binded_mouse_callback );\n\n\telement.addEventListener(&quot;mousewheel&quot;, this._binded_mouse_callback, false);\n\telement.addEventListener(&quot;wheel&quot;, this._binded_mouse_callback, false);\n}\n\nDragAndScale.prototype.computeVisibleArea = function()\n{\n\tif(!this.element)\n\t{\n\t\tthis.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0;\n\t\treturn;\n\t}\n\tvar width = this.element.width;\n\tvar height = this.element.height;\n\tvar startx = -this.offset[0];\n\tvar starty = -this.offset[1];\n\tvar endx = startx + width / this.scale;\n\tvar endy = starty + height / this.scale;\n\tthis.visible_area[0] = startx;\n\tthis.visible_area[1] = starty;\n\tthis.visible_area[2] = endx - startx;\n\tthis.visible_area[3] = endy - starty;\n}\n\nDragAndScale.prototype.onMouse = function(e)\n{\n\tif(!this.enabled)\n\t\treturn;\n\n\tvar canvas = this.element;\n\tvar rect = canvas.getBoundingClientRect();\n\tvar x = e.clientX - rect.left;\n\tvar y = e.clientY - rect.top;\n\te.canvasx = x;\n\te.canvasy = y;\n\te.dragging = this.dragging;\n\n\tvar ignore = false;\n\tif(this.onmouse)\n\t\tignore = this.onmouse(e);\n\n\tif(e.type == &quot;mousedown&quot;)\n\t{\n\t\tthis.dragging = true;\n\t\tcanvas.removeEventListener(&quot;mousemove&quot;, this._binded_mouse_callback );\n\t\tdocument.body.addEventListener(&quot;mousemove&quot;, this._binded_mouse_callback  );\n\t\tdocument.body.addEventListener(&quot;mouseup&quot;, this._binded_mouse_callback );\n\t}\n\telse if(e.type == &quot;mousemove&quot;)\n\t{\n\t\tif(!ignore)\n\t\t{\n\t\t\tvar deltax = x - this.last_mouse[0];\n\t\t\tvar deltay = y - this.last_mouse[1];\n\t\t\tif( this.dragging )\n\t\t\t\tthis.mouseDrag( deltax, deltay );\n\t\t}\n\t}\n\telse if(e.type == &quot;mouseup&quot;)\n\t{\n\t\tthis.dragging = false;\n\t\tdocument.body.removeEventListener(&quot;mousemove&quot;, this._binded_mouse_callback );\n\t\tdocument.body.removeEventListener(&quot;mouseup&quot;, this._binded_mouse_callback );\n\t\tcanvas.addEventListener(&quot;mousemove&quot;, this._binded_mouse_callback  );\n\t}\n\telse if(e.type == &quot;mousewheel&quot; || e.type == &quot;wheel&quot; || e.type == &quot;DOMMouseScroll&quot;)\n\t{ \n\t\te.eventType = &quot;mousewheel&quot;;\n\t\tif(e.type == &quot;wheel&quot;)\n\t\t\te.wheel = -e.deltaY;\n\t\telse\n\t\t\te.wheel = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60);\n\n\t\t//from stack overflow\n\t\te.delta = e.wheelDelta ? e.wheelDelta/40 : e.deltaY ? -e.deltaY/3 : 0;\n\t\tthis.changeDeltaScale(1.0 + e.delta * 0.05);\n\t}\n\n\tthis.last_mouse[0] = x;\n\tthis.last_mouse[1] = y;\n\n\te.preventDefault();\n\te.stopPropagation();\n\treturn false;\n}\n\nDragAndScale.prototype.toCanvasContext = function( ctx )\n{\n\tctx.scale( this.scale, this.scale );\n\tctx.translate( this.offset[0], this.offset[1] );\n}\n\nDragAndScale.prototype.convertOffsetToCanvas = function(pos)\n{\n\t//return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]];\n\treturn [ (pos[0] + this.offset[0]) * this.scale, (pos[1] + this.offset[1]) * this.scale ];\n}\n\nDragAndScale.prototype.convertCanvasToOffset = function(pos, out)\n{\n\tout = out || [0,0];\n\tout[0] = pos[0] / this.scale - this.offset[0];\n\tout[1] = pos[1] / this.scale - this.offset[1];\n\treturn out;\n}\n\nDragAndScale.prototype.mouseDrag = function(x,y)\n{\n\tthis.offset[0] += x / this.scale;\n\tthis.offset[1] += y / this.scale;\n\n\tif(\tthis.onredraw )\n\t\tthis.onredraw( this );\n}\n\nDragAndScale.prototype.changeScale = function( value, zooming_center )\n{\n\tif(value &lt; this.min_scale)\n\t\tvalue = this.min_scale;\n\telse if(value &gt; this.max_scale)\n\t\tvalue = this.max_scale;\n\n\tif(value == this.scale)\n\t\treturn;\n\n\tif(!this.element)\n\t\treturn;\n\n\tvar rect = this.element.getBoundingClientRect();\n\tif(!rect)\n\t\treturn;\n\n\tzooming_center = zooming_center || [rect.width * 0.5,rect.height * 0.5];\n\tvar center = this.convertCanvasToOffset( zooming_center );\n\tthis.scale = value;\n\tif( Math.abs( this.scale - 1 ) &lt; 0.01 )\n\t\tthis.scale = 1;\n\n\tvar new_center = this.convertCanvasToOffset( zooming_center );\n\tvar delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];\n\n\tthis.offset[0] += delta_offset[0];\n\tthis.offset[1] += delta_offset[1];\n\n\tif(\tthis.onredraw )\n\t\tthis.onredraw( this );\n}\n\nDragAndScale.prototype.changeDeltaScale = function( value, zooming_center )\n{\n\tthis.changeScale( this.scale * value, zooming_center );\n}\n\nDragAndScale.prototype.reset = function()\n{\n\tthis.scale = 1;\n\tthis.offset[0] = 0;\n\tthis.offset[1] = 0;\n}\n\n\n//*********************************************************************************\n// LGraphCanvas: LGraph renderer CLASS\n//*********************************************************************************\n\n/**\n* This class is in charge of rendering one graph inside a canvas. And provides all the interaction required.\n* Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked\n*\n* @class LGraphCanvas\n* @constructor\n* @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself)\n* @param {LGraph} graph [optional]\n* @param {Object} options [optional] { skip_rendering, autoresize }\n*/\nfunction LGraphCanvas( canvas, graph, options )\n{\n\toptions = options || {};\n\n\t//if(graph === undefined)\n  //\tthrow (&quot;No graph assigned&quot;);\n\tthis.background_image = &#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=&#x27;\n\n\tif(canvas &amp;&amp; canvas.constructor === String )\n\t\tcanvas = document.querySelector( canvas );\n\n\tthis.ds = new DragAndScale();\n\tthis.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much\n\n\tthis.title_text_font = &quot;&quot;+LiteGraph.NODE_TEXT_SIZE+&quot;px Arial&quot;;\n\tthis.inner_text_font = &quot;normal &quot;+LiteGraph.NODE_SUBTEXT_SIZE+&quot;px Arial&quot;;\n\tthis.node_title_color = LiteGraph.NODE_TITLE_COLOR;\n\tthis.default_link_color = LiteGraph.LINK_COLOR;\n\tthis.default_connection_color = {\n\t\tinput_off: &quot;#778&quot;,\n\t\tinput_on: &quot;#7F7&quot;,\n\t\toutput_off: &quot;#778&quot;,\n\t\toutput_on: &quot;#7F7&quot;\n\t};\n\n\tthis.highquality_render = true;\n\tthis.use_gradients = false; //set to true to render titlebar with gradients\n\tthis.editor_alpha = 1; //used for transition\n\tthis.pause_rendering = false;\n\tthis.clear_background = true;\n\n\tthis.render_only_selected = true;\n\tthis.live_mode = false;\n\tthis.show_info = true;\n\tthis.allow_dragcanvas = true;\n\tthis.allow_dragnodes = true;\n\tthis.allow_interaction = true; //allow to control widgets, buttons, collapse, etc\n\tthis.allow_searchbox = true;\n\tthis.allow_reconnect_links = false; //allows to change a connection with having to redo it again\n\n\tthis.drag_mode = false;\n\tthis.dragging_rectangle = null;\n\n\tthis.filter = null; //allows to filter to only accept some type of nodes in a graph\n\n\tthis.always_render_background = false;\n\tthis.render_shadows = true;\n\tthis.render_canvas_border = true;\n\tthis.render_connections_shadows = false; //too much cpu\n\tthis.render_connections_border = true;\n\tthis.render_curved_connections = false;\n\tthis.render_connection_arrows = false;\n\tthis.render_collapsed_slots = true;\n\tthis.render_execution_order = false;\n\tthis.render_title_colored = true;\n\n\tthis.links_render_mode = LiteGraph.SPLINE_LINK;\n\n\tthis.canvas_mouse = [0,0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle\n\n\t//to personalize the search box\n\tthis.onSearchBox = null;\n\tthis.onSearchBoxSelection = null;\n\n\t//callbacks\n\tthis.onMouse = null;\n\tthis.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform\n\tthis.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform\n\tthis.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs)\n\n\tthis.connections_width = 3;\n\tthis.round_radius = 8;\n\n\tthis.current_node = null;\n\tthis.node_widget = null; //used for widgets\n\tthis.last_mouse_position = [0,0];\n\tthis.visible_area = this.ds.visible_area;\n\tthis.visible_links = [];\n\n\t//link canvas and graph\n\tif(graph)\n\t\tgraph.attachCanvas(this);\n\n\tthis.setCanvas( canvas );\n\tthis.clear();\n\n\tif(!options.skip_render)\n\t\tthis.startRendering();\n\n\tthis.autoresize = options.autoresize;\n}\n\nglobal.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas;\n\nLGraphCanvas.link_type_colors = {&quot;-1&quot;: LiteGraph.EVENT_LINK_COLOR,&#x27;number&#x27;:&quot;#AAA&quot;,&quot;node&quot;:&quot;#DCA&quot;};\nLGraphCanvas.gradients = {}; //cache of gradients\n\n/**\n* clears all the data inside\n*\n* @method clear\n*/\nLGraphCanvas.prototype.clear = function()\n{\n\tthis.frame = 0;\n\tthis.last_draw_time = 0;\n\tthis.render_time = 0;\n\tthis.fps = 0;\n\n\t//this.scale = 1;\n\t//this.offset = [0,0];\n\n\tthis.dragging_rectangle = null;\n\n\tthis.selected_nodes = {};\n\tthis.selected_group = null;\n\n\tthis.visible_nodes = [];\n\tthis.node_dragged = null;\n\tthis.node_over = null;\n\tthis.node_capturing_input = null;\n\tthis.connecting_node = null;\n\tthis.highlighted_links = {};\n\n\tthis.dirty_canvas = true;\n\tthis.dirty_bgcanvas = true;\n\tthis.dirty_area = null;\n\n\tthis.node_in_panel = null;\n\tthis.node_widget = null;\n\n\tthis.last_mouse = [0,0];\n\tthis.last_mouseclick = 0;\n\tthis.visible_area.set([0,0,0,0]);\n\n\tif(this.onClear)\n\t\tthis.onClear();\n\t//this.UIinit();\n}\n\n/**\n* assigns a graph, you can reasign graphs to the same canvas\n*\n* @method setGraph\n* @param {LGraph} graph\n*/\nLGraphCanvas.prototype.setGraph = function( graph, skip_clear )\n{\n\tif(this.graph == graph)\n\t\treturn;\n\n\tif(!skip_clear)\n\t\tthis.clear();\n\n\tif(!graph &amp;&amp; this.graph)\n\t{\n\t\tthis.graph.detachCanvas(this);\n\t\treturn;\n\t}\n\n\t/*\n\tif(this.graph)\n\t\tthis.graph.canvas = null; //remove old graph link to the canvas\n\tthis.graph = graph;\n\tif(this.graph)\n\t\tthis.graph.canvas = this;\n\t*/\n\tgraph.attachCanvas(this);\n\tthis.setDirty(true,true);\n}\n\n/**\n* opens a graph contained inside a node in the current graph\n*\n* @method openSubgraph\n* @param {LGraph} graph\n*/\nLGraphCanvas.prototype.openSubgraph = function(graph)\n{\n\tif(!graph)\n\t\tthrow(&quot;graph cannot be null&quot;);\n\n\tif(this.graph == graph)\n\t\tthrow(&quot;graph cannot be the same&quot;);\n\n\tthis.clear();\n\n\tif(this.graph)\n\t{\n\t\tif(!this._graph_stack)\n\t\t\tthis._graph_stack = [];\n\t\tthis._graph_stack.push(this.graph);\n\t}\n\n\tgraph.attachCanvas(this);\n\tthis.setDirty(true,true);\n}\n\n/**\n* closes a subgraph contained inside a node\n*\n* @method closeSubgraph\n* @param {LGraph} assigns a graph\n*/\nLGraphCanvas.prototype.closeSubgraph = function()\n{\n\tif(!this._graph_stack || this._graph_stack.length == 0)\n\t\treturn;\n\tvar subraph_node = this.graph._subgraph_node;\n\tvar graph = this._graph_stack.pop();\n\tthis.selected_nodes = {};\n\tthis.highlighted_links = {};\n\tgraph.attachCanvas(this);\n\tthis.setDirty(true,true);\n\tif( subraph_node )\n\t{\n\t\tthis.centerOnNode( subraph_node );\n\t\tthis.selectNodes( [subraph_node] );\n\t}\n}\n\n/**\n* assigns a canvas\n*\n* @method setCanvas\n* @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector)\n*/\nLGraphCanvas.prototype.setCanvas = function( canvas, skip_events )\n{\n\tvar that = this;\n\n\tif(canvas)\n\t{\n\t\tif( canvas.constructor === String )\n\t\t{\n\t\t\tcanvas = document.getElementById(canvas);\n\t\t\tif(!canvas)\n\t\t\t\tthrow(&quot;Error creating LiteGraph canvas: Canvas not found&quot;);\n\t\t}\n\t}\n\n\tif(canvas === this.canvas)\n\t\treturn;\n\n\tif(!canvas &amp;&amp; this.canvas)\n\t{\n\t\t//maybe detach events from old_canvas\n\t\tif(!skip_events)\n\t\t\tthis.unbindEvents();\n\t}\n\n\tthis.canvas = canvas;\n\tthis.ds.element = canvas;\n\n\tif(!canvas)\n\t\treturn;\n\n\t//this.canvas.tabindex = &quot;1000&quot;;\n\tcanvas.className += &quot; lgraphcanvas&quot;;\n\tcanvas.data = this;\n\tcanvas.tabindex = &#x27;1&#x27;; //to allow key events\n\n\t//bg canvas: used for non changing stuff\n\tthis.bgcanvas = null;\n\tif(!this.bgcanvas)\n\t{\n\t\tthis.bgcanvas = document.createElement(&quot;canvas&quot;);\n\t\tthis.bgcanvas.width = this.canvas.width;\n\t\tthis.bgcanvas.height = this.canvas.height;\n\t}\n\n\tif(canvas.getContext == null)\n\t{\n\t\tif( canvas.localName != &quot;canvas&quot; )\n\t\t\tthrow(&quot;Element supplied for LGraphCanvas must be a &lt;canvas&gt; element, you passed a &quot; + canvas.localName );\n\t\tthrow(&quot;This browser doesnt support Canvas&quot;);\n\t}\n\n\tvar ctx = this.ctx = canvas.getContext(&quot;2d&quot;);\n\tif(ctx == null)\n\t{\n\t\tif(!canvas.webgl_enabled)\n\t\t\tconsole.warn(&quot;This canvas seems to be WebGL, enabling WebGL renderer&quot;);\n\t\tthis.enableWebGL();\n\t}\n\n\t//input:  (move and up could be unbinded)\n\tthis._mousemove_callback = this.processMouseMove.bind(this);\n\tthis._mouseup_callback = this.processMouseUp.bind(this);\n\n\tif(!skip_events)\n\t\tthis.bindEvents();\n}\n\n//used in some events to capture them\nLGraphCanvas.prototype._doNothing = function doNothing(e) { e.preventDefault(); return false; };\nLGraphCanvas.prototype._doReturnTrue = function doNothing(e) { e.preventDefault(); return true; };\n\n/**\n* binds mouse, keyboard, touch and drag events to the canvas\n* @method bindEvents\n**/\nLGraphCanvas.prototype.bindEvents = function()\n{\n\tif(\tthis._events_binded )\n\t{\n\t\tconsole.warn(&quot;LGraphCanvas: events already binded&quot;);\n\t\treturn;\n\t}\n\n\tvar canvas = this.canvas;\n\tvar ref_window = this.getCanvasWindow();\n\tvar document = ref_window.document; //hack used when moving canvas between windows\n\n\tthis._mousedown_callback = this.processMouseDown.bind(this);\n\tthis._mousewheel_callback = this.processMouseWheel.bind(this);\n\n\tcanvas.addEventListener(&quot;mousedown&quot;, this._mousedown_callback, true ); //down do not need to store the binded\n\tcanvas.addEventListener(&quot;mousemove&quot;, this._mousemove_callback );\n\tcanvas.addEventListener(&quot;mousewheel&quot;, this._mousewheel_callback, false);\n\n\tcanvas.addEventListener(&quot;contextmenu&quot;, this._doNothing );\n\tcanvas.addEventListener(&quot;DOMMouseScroll&quot;, this._mousewheel_callback, false);\n\n\t//touch events\n\t//if( &#x27;touchstart&#x27; in document.documentElement )\n\t{\n\t\tcanvas.addEventListener(&quot;touchstart&quot;, this.touchHandler, true);\n\t\tcanvas.addEventListener(&quot;touchmove&quot;, this.touchHandler, true);\n\t\tcanvas.addEventListener(&quot;touchend&quot;, this.touchHandler, true);\n\t\tcanvas.addEventListener(&quot;touchcancel&quot;, this.touchHandler, true);\n\t}\n\n\t//Keyboard ******************\n\tthis._key_callback = this.processKey.bind(this);\n\n\tcanvas.addEventListener(&quot;keydown&quot;, this._key_callback, true );\n\tdocument.addEventListener(&quot;keyup&quot;, this._key_callback, true ); //in document, otherwise it doesnt fire keyup\n\n\t//Droping Stuff over nodes ************************************\n\tthis._ondrop_callback = this.processDrop.bind(this);\n\n\tcanvas.addEventListener(&quot;dragover&quot;, this._doNothing, false );\n\tcanvas.addEventListener(&quot;dragend&quot;, this._doNothing, false );\n\tcanvas.addEventListener(&quot;drop&quot;, this._ondrop_callback, false );\n\tcanvas.addEventListener(&quot;dragenter&quot;, this._doReturnTrue, false );\n\n\tthis._events_binded = true;\n}\n\n/**\n* unbinds mouse events from the canvas\n* @method unbindEvents\n**/\nLGraphCanvas.prototype.unbindEvents = function()\n{\n\tif(\t!this._events_binded )\n\t{\n\t\tconsole.warn(&quot;LGraphCanvas: no events binded&quot;);\n\t\treturn;\n\t}\n\n\tvar ref_window = this.getCanvasWindow();\n\tvar document = ref_window.document;\n\n\tthis.canvas.removeEventListener( &quot;mousedown&quot;, this._mousedown_callback );\n\tthis.canvas.removeEventListener( &quot;mousewheel&quot;, this._mousewheel_callback );\n\tthis.canvas.removeEventListener( &quot;DOMMouseScroll&quot;, this._mousewheel_callback );\n\tthis.canvas.removeEventListener( &quot;keydown&quot;, this._key_callback );\n\tdocument.removeEventListener( &quot;keyup&quot;, this._key_callback );\n\tthis.canvas.removeEventListener( &quot;contextmenu&quot;, this._doNothing );\n\tthis.canvas.removeEventListener( &quot;drop&quot;, this._ondrop_callback );\n\tthis.canvas.removeEventListener( &quot;dragenter&quot;, this._doReturnTrue );\n\n\tthis.canvas.removeEventListener(&quot;touchstart&quot;, this.touchHandler );\n\tthis.canvas.removeEventListener(&quot;touchmove&quot;, this.touchHandler );\n\tthis.canvas.removeEventListener(&quot;touchend&quot;, this.touchHandler );\n\tthis.canvas.removeEventListener(&quot;touchcancel&quot;, this.touchHandler );\n\n\tthis._mousedown_callback = null;\n\tthis._mousewheel_callback = null;\n\tthis._key_callback = null;\n\tthis._ondrop_callback = null;\n\n\tthis._events_binded = false;\n}\n\nLGraphCanvas.getFileExtension = function (url)\n{\n\tvar question = url.indexOf(&quot;?&quot;);\n\tif(question != -1)\n\t\turl = url.substr(0,question);\n\tvar point = url.lastIndexOf(&quot;.&quot;);\n\tif(point == -1)\n\t\treturn &quot;&quot;;\n\treturn url.substr(point+1).toLowerCase();\n}\n\n/**\n* this function allows to render the canvas using WebGL instead of Canvas2D\n* this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL\n* @method enableWebGL\n**/\nLGraphCanvas.prototype.enableWebGL = function()\n{\n\tif(typeof(GL) === undefined)\n\t\tthrow(&quot;litegl.js must be included to use a WebGL canvas&quot;);\n\tif(typeof(enableWebGLCanvas) === undefined)\n\t\tthrow(&quot;webglCanvas.js must be included to use this feature&quot;);\n\n\tthis.gl = this.ctx = enableWebGLCanvas(this.canvas);\n\tthis.ctx.webgl = true;\n\tthis.bgcanvas = this.canvas;\n\tthis.bgctx = this.gl;\n\tthis.canvas.webgl_enabled = true;\n\n\t/*\n\tGL.create({ canvas: this.bgcanvas });\n\tthis.bgctx = enableWebGLCanvas( this.bgcanvas );\n\twindow.gl = this.gl;\n\t*/\n}\n\n\n/**\n* marks as dirty the canvas, this way it will be rendered again\n*\n* @class LGraphCanvas\n* @method setDirty\n* @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes)\n* @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires)\n*/\nLGraphCanvas.prototype.setDirty = function( fgcanvas, bgcanvas )\n{\n\tif(fgcanvas)\n\t\tthis.dirty_canvas = true;\n\tif(bgcanvas)\n\t\tthis.dirty_bgcanvas = true;\n}\n\n/**\n* Used to attach the canvas in a popup\n*\n* @method getCanvasWindow\n* @return {window} returns the window where the canvas is attached (the DOM root node)\n*/\nLGraphCanvas.prototype.getCanvasWindow = function()\n{\n\tif(!this.canvas)\n\t\treturn window;\n\tvar doc = this.canvas.ownerDocument;\n\treturn doc.defaultView || doc.parentWindow;\n}\n\n/**\n* starts rendering the content of the canvas when needed\n*\n* @method startRendering\n*/\nLGraphCanvas.prototype.startRendering = function()\n{\n\tif(this.is_rendering)\n\t\treturn; //already rendering\n\n\tthis.is_rendering = true;\n\trenderFrame.call(this);\n\n\tfunction renderFrame()\n\t{\n\t\tif(!this.pause_rendering)\n\t\t\tthis.draw();\n\n\t\tvar window = this.getCanvasWindow();\n\t\tif(this.is_rendering)\n\t\t\twindow.requestAnimationFrame( renderFrame.bind(this) );\n\t}\n}\n\n/**\n* stops rendering the content of the canvas (to save resources)\n*\n* @method stopRendering\n*/\nLGraphCanvas.prototype.stopRendering = function()\n{\n\tthis.is_rendering = false;\n\t/*\n\tif(this.rendering_timer_id)\n\t{\n\t\tclearInterval(this.rendering_timer_id);\n\t\tthis.rendering_timer_id = null;\n\t}\n\t*/\n}\n\n/* LiteGraphCanvas input */\n\nLGraphCanvas.prototype.processMouseDown = function(e)\n{\n\tif(!this.graph)\n\t\treturn;\n\n\tthis.adjustMouseEvent(e);\n\n\tvar ref_window = this.getCanvasWindow();\n\tvar document = ref_window.document;\n\tLGraphCanvas.active_canvas = this;\n\tvar that = this;\n\n\t//move mouse move event to the window in case it drags outside of the canvas\n\tthis.canvas.removeEventListener(&quot;mousemove&quot;, this._mousemove_callback );\n\tref_window.document.addEventListener(&quot;mousemove&quot;, this._mousemove_callback, true ); //catch for the entire window\n\tref_window.document.addEventListener(&quot;mouseup&quot;, this._mouseup_callback, true );\n\n\tvar node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 );\n\tvar skip_dragging = false;\n\tvar skip_action = false;\n\tvar now = LiteGraph.getTime();\n\tvar is_double_click = (now - this.last_mouseclick) &lt; 300;\n\n\tthis.canvas_mouse[0] = e.canvasX;\n\tthis.canvas_mouse[1] = e.canvasY;\n\tthis.canvas.focus();\n\n    LiteGraph.closeAllContextMenus( ref_window );\n\n\tif(this.onMouse)\n\t{\n\t\tif( this.onMouse(e) == true )\n\t\t\treturn;\n\t}\n\n\tif(e.which == 1) //left button mouse\n\t{\n\t\tif( e.ctrlKey )\n\t\t{\n\t\t\tthis.dragging_rectangle = new Float32Array(4);\n\t\t\tthis.dragging_rectangle[0] = e.canvasX;\n\t\t\tthis.dragging_rectangle[1] = e.canvasY;\n\t\t\tthis.dragging_rectangle[2] = 1;\n\t\t\tthis.dragging_rectangle[3] = 1;\n\t\t\tskip_action = true;\n\t\t}\n\n\t\tvar clicking_canvas_bg = false;\n\n\t\t//when clicked on top of a node\n\t\t//and it is not interactive\n\t\tif( node &amp;&amp; this.allow_interaction &amp;&amp; !skip_action )\n\t\t{\n\t\t\tif( !this.live_mode &amp;&amp; !node.flags.pinned )\n\t\t\t\tthis.bringToFront( node ); //if it wasnt selected?\n\n\t\t\t//not dragging mouse to connect two slots\n\t\t\tif(!this.connecting_node &amp;&amp; !node.flags.collapsed &amp;&amp; !this.live_mode)\n\t\t\t{\n\t\t\t\t//Search for corner for resize\n\t\t\t\tif( !skip_action &amp;&amp; node.resizable !== false &amp;&amp; isInsideRectangle( e.canvasX, e.canvasY, node.pos[0] + node.size[0] - 5, node.pos[1] + node.size[1] - 5 ,10,10 ))\n\t\t\t\t{\n\t\t\t\t\tthis.resizing_node = node;\n\t\t\t\t\tthis.canvas.style.cursor = &quot;se-resize&quot;;\n\t\t\t\t\tskip_action = true;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t//search for outputs\n\t\t\t\t\tif(node.outputs)\n\t\t\t\t\t\tfor(var i = 0, l = node.outputs.length; i &lt; l; ++i)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar output = node.outputs[i];\n\t\t\t\t\t\t\tvar link_pos = node.getConnectionPos(false,i);\n\t\t\t\t\t\t\tif( isInsideRectangle( e.canvasX, e.canvasY, link_pos[0] - 15, link_pos[1] - 10, 30,20) )\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis.connecting_node = node;\n\t\t\t\t\t\t\t\tthis.connecting_output = output;\n\t\t\t\t\t\t\t\tthis.connecting_pos = node.getConnectionPos(false,i);\n\t\t\t\t\t\t\t\tthis.connecting_slot = i;\n\n\t\t\t\t\t\t\t\tif( e.shiftKey )\n\t\t\t\t\t\t\t\t\tnode.disconnectOutput(i);\n\n\t\t\t\t\t\t\t\tif (is_double_click) {\n\t\t\t\t\t\t\t\t\tif (node.onOutputDblClick)\n\t\t\t\t\t\t\t\t\t\tnode.onOutputDblClick(i, e);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tif (node.onOutputClick)\n\t\t\t\t\t\t\t\t\t\tnode.onOutputClick(i, e);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tskip_action = true;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t//search for inputs\n\t\t\t\t\tif(node.inputs)\n\t\t\t\t\t\tfor(var i = 0, l = node.inputs.length; i &lt; l; ++i)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar input = node.inputs[i];\n\t\t\t\t\t\t\tvar link_pos = node.getConnectionPos( true, i );\n\t\t\t\t\t\t\tif( isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 15, link_pos[1] - 10, 30,20) )\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (is_double_click) {\n\t\t\t\t\t\t\t\t\tif (node.onInputDblClick)\n\t\t\t\t\t\t\t\t\t\tnode.onInputDblClick(i, e);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tif (node.onInputClick)\n\t\t\t\t\t\t\t\t\t\tnode.onInputClick(i, e);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif(input.link !== null)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tvar link_info = this.graph.links[ input.link ]; //before disconnecting\n\t\t\t\t\t\t\t\t\tnode.disconnectInput(i);\n\n\t\t\t\t\t\t\t\t\tif( this.allow_reconnect_links || e.shiftKey )\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tthis.connecting_node = this.graph._nodes_by_id[ link_info.origin_id ];\n\t\t\t\t\t\t\t\t\t\tthis.connecting_slot = link_info.origin_slot;\n\t\t\t\t\t\t\t\t\t\tthis.connecting_output = this.connecting_node.outputs[ this.connecting_slot ];\n\t\t\t\t\t\t\t\t\t\tthis.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot );\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tthis.dirty_bgcanvas = true;\n\t\t\t\t\t\t\t\t\tskip_action = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t} //not resizing\n\t\t\t}\n\n\t\t\t//Search for corner for collapsing\n\t\t\t/*\n\t\t\tif( !skip_action &amp;&amp; isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ))\n\t\t\t{\n\t\t\t\tnode.collapse();\n\t\t\t\tskip_action = true;\n\t\t\t}\n\t\t\t*/\n\n\t\t\t//it wasnt clicked on the links boxes\n\t\t\tif(!skip_action)\n\t\t\t{\n\t\t\t\tvar block_drag_node = false;\n\n\t\t\t\t//widgets\n\t\t\t\tvar widget = this.processNodeWidgets( node, this.canvas_mouse, e );\n\t\t\t\tif(widget)\n\t\t\t\t{\n\t\t\t\t\tblock_drag_node = true;\n\t\t\t\t\tthis.node_widget = [node, widget];\n\t\t\t\t}\n\n\t\t\t\t//double clicking\n\t\t\t\tif (is_double_click &amp;&amp; this.selected_nodes[ node.id ])\n\t\t\t\t{\n\t\t\t\t\t//double click node\n\t\t\t\t\tif( node.onDblClick)\n\t\t\t\t\t\tnode.onDblClick(e,[e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this);\n\t\t\t\t\tthis.processNodeDblClicked( node );\n\t\t\t\t\tblock_drag_node = true;\n\t\t\t\t}\n\n\t\t\t\t//if do not capture mouse\n\t\t\t\tif( node.onMouseDown &amp;&amp; node.onMouseDown( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ) )\n\t\t\t\t{\n\t\t\t\t\tblock_drag_node = true;\n\t\t\t\t}\n\t\t\t\telse if(this.live_mode)\n\t\t\t\t{\n\t\t\t\t\tclicking_canvas_bg = true;\n\t\t\t\t\tblock_drag_node = true;\n\t\t\t\t}\n\n\t\t\t\tif(!block_drag_node)\n\t\t\t\t{\n\t\t\t\t\tif(this.allow_dragnodes)\n\t\t\t\t\t\tthis.node_dragged = node;\n\t\t\t\t\tif(!this.selected_nodes[ node.id ])\n\t\t\t\t\t\tthis.processNodeSelected( node, e );\n\t\t\t\t}\n\n\t\t\t\tthis.dirty_canvas = true;\n\t\t\t}\n\t\t}\n\t\telse //clicked outside of nodes\n\t\t{\n\n\t\t\t//search for link connector\n\t\t\tfor(var i = 0; i &lt; this.visible_links.length; ++i)\n\t\t\t{\n\t\t\t\tvar link = this.visible_links[i];\n\t\t\t\tvar center = link._pos;\n\t\t\t\tif( !center || e.canvasX &lt; center[0] - 4 || e.canvasX &gt; center[0] + 4 || e.canvasY &lt; center[1] - 4 || e.canvasY &gt; center[1] + 4 )\n\t\t\t\t\tcontinue;\n\t\t\t\t//link clicked\n\t\t\t\tthis.showLinkMenu( link, e );\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tthis.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY );\n\t\t\tthis.selected_group_resizing = false;\n\t\t\tif( this.selected_group )\n\t\t\t{\n\t\t\t\tif( e.ctrlKey )\n\t\t\t\t\tthis.dragging_rectangle = null;\n\n\t\t\t\tvar dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] );\n\t\t\t\tif( (dist * this.ds.scale) &lt; 10 )\n\t\t\t\t\tthis.selected_group_resizing = true;\n\t\t\t\telse\n\t\t\t\t\tthis.selected_group.recomputeInsideNodes();\n\t\t\t}\n\n\t\t\tif( is_double_click )\n\t\t\t\tthis.showSearchBox( e );\n\t\t\t\n\t\t\tclicking_canvas_bg = true;\n\t\t}\n\n\t\tif( !skip_action &amp;&amp; clicking_canvas_bg &amp;&amp; this.allow_dragcanvas )\n\t\t{\n\t\t\tthis.dragging_canvas = true;\n\t\t}\n\t}\n\telse if (e.which == 2) //middle button\n\t{\n\n\t}\n\telse if (e.which == 3) //right button\n\t{\n\t\tthis.processContextMenu( node, e );\n\t}\n\n\t//TODO\n\t//if(this.node_selected != prev_selected)\n\t//\tthis.onNodeSelectionChange(this.node_selected);\n\n\tthis.last_mouse[0] = e.localX;\n\tthis.last_mouse[1] = e.localY;\n\tthis.last_mouseclick = LiteGraph.getTime();\n\tthis.last_mouse_dragging = true;\n\n\t/*\n\tif( (this.dirty_canvas || this.dirty_bgcanvas) &amp;&amp; this.rendering_timer_id == null)\n\t\tthis.draw();\n\t*/\n\n\tthis.graph.change();\n\n\t//this is to ensure to defocus(blur) if a text input element is on focus\n\tif(!ref_window.document.activeElement || (ref_window.document.activeElement.nodeName.toLowerCase() != &quot;input&quot; &amp;&amp; ref_window.document.activeElement.nodeName.toLowerCase() != &quot;textarea&quot;))\n\t\te.preventDefault();\n\te.stopPropagation();\n\n\tif(this.onMouseDown)\n\t\tthis.onMouseDown(e);\n\n\treturn false;\n}\n\n/**\n* Called when a mouse move event has to be processed\n* @method processMouseMove\n**/\nLGraphCanvas.prototype.processMouseMove = function(e)\n{\n\tif(this.autoresize)\n\t\tthis.resize();\n\n\tif(!this.graph)\n\t\treturn;\n\n\tLGraphCanvas.active_canvas = this;\n\tthis.adjustMouseEvent(e);\n\tvar mouse = [e.localX, e.localY];\n\tvar delta = [mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1]];\n\tthis.last_mouse = mouse;\n\tthis.canvas_mouse[0] = e.canvasX;\n\tthis.canvas_mouse[1] = e.canvasY;\n\te.dragging = this.last_mouse_dragging;\n\n\tif( this.node_widget )\n\t{\n\t\tthis.processNodeWidgets( this.node_widget[0], this.canvas_mouse, e, this.node_widget[1] );\n\t\tthis.dirty_canvas = true;\n\t}\n\n\tif( this.dragging_rectangle )\n\t{\n\t\tthis.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0];\n\t\tthis.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1];\n\t\tthis.dirty_canvas = true;\n\t}\n\telse if (this.selected_group) //moving/resizing a group\n\t{\n\t\tif( this.selected_group_resizing )\n\t\t\tthis.selected_group.size = [ e.canvasX - this.selected_group.pos[0], e.canvasY - this.selected_group.pos[1] ];\n\t\telse\n\t\t{\n\t\t\tvar deltax = delta[0] / this.ds.scale;\n\t\t\tvar deltay = delta[1] / this.ds.scale;\n\t\t\tthis.selected_group.move( deltax, deltay, e.ctrlKey );\n\t\t\tif( this.selected_group._nodes.length)\n\t\t\t\tthis.dirty_canvas = true;\n\t\t}\n\t\tthis.dirty_bgcanvas = true;\n\t}\n\telse if(this.dragging_canvas)\n\t{\n\t\tthis.ds.offset[0] += delta[0] / this.ds.scale;\n\t\tthis.ds.offset[1] += delta[1] / this.ds.scale;\n\t\tthis.dirty_canvas = true;\n\t\tthis.dirty_bgcanvas = true;\n\t}\n\telse if(this.allow_interaction)\n\t{\n\t\tif(this.connecting_node)\n\t\t\tthis.dirty_canvas = true;\n\n\t\t//get node over\n\t\tvar node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );\n\n\t\t//remove mouseover flag\n\t\tfor(var i = 0, l = this.graph._nodes.length; i &lt; l; ++i)\n\t\t{\n\t\t\tif(this.graph._nodes[i].mouseOver &amp;&amp; node != this.graph._nodes[i])\n\t\t\t{\n\t\t\t\t//mouse leave\n\t\t\t\tthis.graph._nodes[i].mouseOver = false;\n\t\t\t\tif(this.node_over &amp;&amp; this.node_over.onMouseLeave)\n\t\t\t\t\tthis.node_over.onMouseLeave(e);\n\t\t\t\tthis.node_over = null;\n\t\t\t\tthis.dirty_canvas = true;\n\t\t\t}\n\t\t}\n\n\t\t//mouse over a node\n\t\tif(node)\n\t\t{\n\t\t\t//this.canvas.style.cursor = &quot;move&quot;;\n\t\t\tif(!node.mouseOver)\n\t\t\t{\n\t\t\t\t//mouse enter\n\t\t\t\tnode.mouseOver = true;\n\t\t\t\tthis.node_over = node;\n\t\t\t\tthis.dirty_canvas = true;\n\n\t\t\t\tif(node.onMouseEnter) node.onMouseEnter(e);\n\t\t\t}\n\n\t\t\t//in case the node wants to do something\n\t\t\tif(node.onMouseMove)\n\t\t\t\tnode.onMouseMove(e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this);\n\n\t\t\t//if dragging a link \n\t\t\tif(this.connecting_node)\n\t\t\t{\n\t\t\t\tvar pos = this._highlight_input || [0,0]; //to store the output of isOverNodeInput\n\n\t\t\t\t//on top of input\n\t\t\t\tif( this.isOverNodeBox( node, e.canvasX, e.canvasY ) )\n\t\t\t\t{\n\t\t\t\t\t//mouse on top of the corner box, dont know what to do\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t//check if I have a slot below de mouse\n\t\t\t\t\tvar slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos );\n\t\t\t\t\tif(slot != -1 &amp;&amp; node.inputs[slot] )\n\t\t\t\t\t{\n\t\t\t\t\t\tvar slot_type = node.inputs[slot].type;\n\t\t\t\t\t\tif( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) )\n\t\t\t\t\t\t\tthis._highlight_input = pos;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tthis._highlight_input = null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//Search for corner\n\t\t\tif(this.canvas)\n\t\t\t{\n\t\t\t\tif( isInsideRectangle(e.canvasX, e.canvasY, node.pos[0] + node.size[0] - 5, node.pos[1] + node.size[1] - 5 ,5,5 ))\n\t\t\t\t\tthis.canvas.style.cursor = &quot;se-resize&quot;;\n\t\t\t\telse\n\t\t\t\t\tthis.canvas.style.cursor = &quot;crosshair&quot;;\n\t\t\t}\n\t\t}\n\t\telse if(this.canvas)\n\t\t\tthis.canvas.style.cursor = &quot;&quot;;\n\n\t\tif(this.node_capturing_input &amp;&amp; this.node_capturing_input != node &amp;&amp; this.node_capturing_input.onMouseMove)\n\t\t{\n\t\t\tthis.node_capturing_input.onMouseMove(e);\n\t\t}\n\n\n\t\tif(this.node_dragged &amp;&amp; !this.live_mode)\n\t\t{\n\t\t\tfor(var i in this.selected_nodes)\n\t\t\t{\n\t\t\t\tvar n = this.selected_nodes[i];\n\t\t\t\tn.pos[0] += delta[0] / this.ds.scale;\n\t\t\t\tn.pos[1] += delta[1] / this.ds.scale;\n\t\t\t}\n\n\t\t\tthis.dirty_canvas = true;\n\t\t\tthis.dirty_bgcanvas = true;\n\t\t}\n\n\t\tif(this.resizing_node &amp;&amp; !this.live_mode)\n\t\t{\n\t\t\t//convert mouse to node space\n\t\t\tthis.resizing_node.size[0] = e.canvasX - this.resizing_node.pos[0];\n\t\t\tthis.resizing_node.size[1] = e.canvasY - this.resizing_node.pos[1];\n\n\t\t\t//constraint size\n\t\t\tvar max_slots = Math.max( this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, this.resizing_node.outputs ? this.resizing_node.outputs.length : 0);\n\t\t\tvar min_height = max_slots * LiteGraph.NODE_SLOT_HEIGHT + ( this.resizing_node.widgets ? this.resizing_node.widgets.length : 0 ) * (LiteGraph.NODE_WIDGET_HEIGHT + 4 ) + 4;\n\t\t\tif(this.resizing_node.size[1] &lt; min_height )\n\t\t\t\tthis.resizing_node.size[1] = min_height;\n\t\t\tif(this.resizing_node.size[0] &lt; LiteGraph.NODE_MIN_WIDTH)\n\t\t\t\tthis.resizing_node.size[0] = LiteGraph.NODE_MIN_WIDTH;\n\n\t\t\tthis.canvas.style.cursor = &quot;se-resize&quot;;\n\t\t\tthis.dirty_canvas = true;\n\t\t\tthis.dirty_bgcanvas = true;\n\t\t}\n\t}\n\n\te.preventDefault();\n\treturn false;\n}\n\n/**\n* Called when a mouse up event has to be processed\n* @method processMouseUp\n**/\nLGraphCanvas.prototype.processMouseUp = function(e)\n{\n\tif(!this.graph)\n\t\treturn;\n\n\tvar window = this.getCanvasWindow();\n\tvar document = window.document;\n\tLGraphCanvas.active_canvas = this;\n\n\t//restore the mousemove event back to the canvas\n\tdocument.removeEventListener(&quot;mousemove&quot;, this._mousemove_callback, true );\n\tthis.canvas.addEventListener(&quot;mousemove&quot;, this._mousemove_callback, true);\n\tdocument.removeEventListener(&quot;mouseup&quot;, this._mouseup_callback, true );\n\n\tthis.adjustMouseEvent(e);\n\tvar now = LiteGraph.getTime();\n\te.click_time = (now - this.last_mouseclick);\n\tthis.last_mouse_dragging = false;\n\n\tif (e.which == 1) //left button\n\t{\n\t\tthis.node_widget = null;\n\n\t\tif( this.selected_group )\n\t\t{\n\t\t\tvar diffx = this.selected_group.pos[0] - Math.round( this.selected_group.pos[0] );\n\t\t\tvar diffy = this.selected_group.pos[1] - Math.round( this.selected_group.pos[1] );\n\t\t\tthis.selected_group.move( diffx, diffy, e.ctrlKey );\n\t\t\tthis.selected_group.pos[0] = Math.round( this.selected_group.pos[0] );\n\t\t\tthis.selected_group.pos[1] = Math.round( this.selected_group.pos[1] );\n\t\t\tif( this.selected_group._nodes.length )\n\t\t\t\tthis.dirty_canvas = true;\n\t\t\tthis.selected_group = null;\n\t\t}\n\t\tthis.selected_group_resizing = false;\n\n\t\tif( this.dragging_rectangle )\n\t\t{\n\t\t\tif(this.graph)\n\t\t\t{\n\t\t\t\tvar nodes = this.graph._nodes;\n\t\t\t\tvar node_bounding = new Float32Array(4);\n\t\t\t\tthis.deselectAllNodes();\n\t\t\t\t//compute bounding and flip if left to right\n\t\t\t\tvar w = Math.abs( this.dragging_rectangle[2] );\n\t\t\t\tvar h = Math.abs( this.dragging_rectangle[3] );\n\t\t\t\tvar startx = this.dragging_rectangle[2] &lt; 0 ? this.dragging_rectangle[0] - w : this.dragging_rectangle[0];\n\t\t\t\tvar starty = this.dragging_rectangle[3] &lt; 0 ? this.dragging_rectangle[1] - h : this.dragging_rectangle[1];\n\t\t\t\tthis.dragging_rectangle[0] = startx; this.dragging_rectangle[1] = starty; this.dragging_rectangle[2] = w; this.dragging_rectangle[3] = h;\n\n\t\t\t\t//test against all nodes (not visible becasue the rectangle maybe start outside\n\t\t\t\tvar to_select = [];\n\t\t\t\tfor(var i = 0; i &lt; nodes.length; ++i)\n\t\t\t\t{\n\t\t\t\t\tvar node = nodes[i];\n\t\t\t\t\tnode.getBounding( node_bounding );\n\t\t\t\t\tif(!overlapBounding( this.dragging_rectangle, node_bounding ))\n\t\t\t\t\t\tcontinue; //out of the visible area\n\t\t\t\t\tto_select.push(node);\n\t\t\t\t}\n\t\t\t\tif(to_select.length)\n\t\t\t\t\tthis.selectNodes(to_select);\n\t\t\t}\n\t\t\tthis.dragging_rectangle = null;\n\t\t}\n\t\telse if(this.connecting_node) //dragging a connection\n\t\t{\n\t\t\tthis.dirty_canvas = true;\n\t\t\tthis.dirty_bgcanvas = true;\n\n\t\t\tvar node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );\n\n\t\t\t//node below mouse\n\t\t\tif(node)\n\t\t\t{\n\t\t\t\tif( this.connecting_output.type == LiteGraph.EVENT &amp;&amp; this.isOverNodeBox( node, e.canvasX, e.canvasY ) )\n\t\t\t\t{\n\t\t\t\t\tthis.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t//slot below mouse? connect\n\t\t\t\t\tvar slot = this.isOverNodeInput(node, e.canvasX, e.canvasY);\n\t\t\t\t\tif(slot != -1)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis.connecting_node.connect(this.connecting_slot, node, slot);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{ //not on top of an input\n\t\t\t\t\t\tvar input = node.getInputInfo(0);\n\t\t\t\t\t\t//auto connect\n\t\t\t\t\t\tif(this.connecting_output.type == LiteGraph.EVENT)\n\t\t\t\t\t\t\tthis.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT );\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tif(input &amp;&amp; !input.link &amp;&amp; LiteGraph.isValidConnection( input.type &amp;&amp; this.connecting_output.type ) )\n\t\t\t\t\t\t\t\tthis.connecting_node.connect( this.connecting_slot, node, 0 );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.connecting_output = null;\n\t\t\tthis.connecting_pos = null;\n\t\t\tthis.connecting_node = null;\n\t\t\tthis.connecting_slot = -1;\n\n\t\t}//not dragging connection\n\t\telse if(this.resizing_node)\n\t\t{\n\t\t\tthis.dirty_canvas = true;\n\t\t\tthis.dirty_bgcanvas = true;\n\t\t\tthis.resizing_node = null;\n\t\t}\n\t\telse if(this.node_dragged) //node being dragged?\n\t\t{\n\t\t\tvar node = this.node_dragged;\n\t\t\tif( node &amp;&amp; e.click_time &lt; 300 &amp;&amp; isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ))\n\t\t\t\tnode.collapse();\n\n\t\t\tthis.dirty_canvas = true;\n\t\t\tthis.dirty_bgcanvas = true;\n\t\t\tthis.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]);\n\t\t\tthis.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]);\n\t\t\tif(this.graph.config.align_to_grid)\n\t\t\t\tthis.node_dragged.alignToGrid();\n\t\t\tthis.node_dragged = null;\n\t\t}\n\t\telse //no node being dragged\n\t\t{\n\t\t\t//get node over\n\t\t\tvar node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );\n\n\t\t\tif ( !node &amp;&amp; e.click_time &lt; 300 )\n\t\t\t\tthis.deselectAllNodes();\n\n\t\t\tthis.dirty_canvas = true;\n\t\t\tthis.dragging_canvas = false;\n\n\t\t\tif( this.node_over &amp;&amp; this.node_over.onMouseUp )\n\t\t\t\tthis.node_over.onMouseUp(e, [e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1]], this );\n\t\t\tif( this.node_capturing_input &amp;&amp; this.node_capturing_input.onMouseUp )\n\t\t\t\tthis.node_capturing_input.onMouseUp(e, [e.canvasX - this.node_capturing_input.pos[0], e.canvasY - this.node_capturing_input.pos[1]] );\n\t\t}\n\t}\n\telse if (e.which == 2) //middle button\n\t{\n\t\t//trace(&quot;middle&quot;);\n\t\tthis.dirty_canvas = true;\n\t\tthis.dragging_canvas = false;\n\t}\n\telse if (e.which == 3) //right button\n\t{\n\t\t//trace(&quot;right&quot;);\n\t\tthis.dirty_canvas = true;\n\t\tthis.dragging_canvas = false;\n\t}\n\n\t/*\n\tif((this.dirty_canvas || this.dirty_bgcanvas) &amp;&amp; this.rendering_timer_id == null)\n\t\tthis.draw();\n\t*/\n\n\tthis.graph.change();\n\n\te.stopPropagation();\n\te.preventDefault();\n\treturn false;\n}\n\n/**\n* Called when a mouse wheel event has to be processed\n* @method processMouseWheel\n**/\nLGraphCanvas.prototype.processMouseWheel = function(e)\n{\n\tif(!this.graph || !this.allow_dragcanvas)\n\t\treturn;\n\n\tvar delta = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60);\n\n\tthis.adjustMouseEvent(e);\n\n\tvar scale = this.ds.scale;\n\n\tif (delta &gt; 0)\n\t\tscale *= 1.1;\n\telse if (delta &lt; 0)\n\t\tscale *= 1/(1.1);\n\n\t//this.setZoom( scale, [ e.localX, e.localY ] );\n\tthis.ds.changeScale( scale, [ e.localX, e.localY ] );\n\n\tthis.graph.change();\n\n\te.preventDefault();\n\treturn false; // prevent default\n}\n\n/**\n* retuns true if a position (in graph space) is on top of a node little corner box\n* @method isOverNodeBox\n**/\nLGraphCanvas.prototype.isOverNodeBox = function( node, canvasx, canvasy )\n{\n\tvar title_height = LiteGraph.NODE_TITLE_HEIGHT;\n\tif( isInsideRectangle( canvasx, canvasy, node.pos[0] + 2, node.pos[1] + 2 - title_height, title_height - 4, title_height - 4) )\n\t\treturn true;\n\treturn false;\n}\n\n/**\n* retuns true if a position (in graph space) is on top of a node input slot\n* @method isOverNodeInput\n**/\nLGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos )\n{\n\tif(node.inputs)\n\t\tfor(var i = 0, l = node.inputs.length; i &lt; l; ++i)\n\t\t{\n\t\t\tvar input = node.inputs[i];\n\t\t\tvar link_pos = node.getConnectionPos( true, i );\n\t\t\tvar is_inside = false;\n\t\t\tif( node.horizontal )\n\t\t\t\tis_inside = isInsideRectangle(canvasx, canvasy, link_pos[0] - 5, link_pos[1] - 10, 10,20)\n\t\t\telse\n\t\t\t\tis_inside = isInsideRectangle(canvasx, canvasy, link_pos[0] - 10, link_pos[1] - 5, 40,10)\n\t\t\tif(is_inside)\n\t\t\t{\n\t\t\t\tif(slot_pos)\n\t\t\t\t{\n\t\t\t\t\tslot_pos[0] = link_pos[0];\n\t\t\t\t\tslot_pos[1] = link_pos[1];\n\t\t\t\t}\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\treturn -1;\n}\n\n/**\n* process a key event\n* @method processKey\n**/\nLGraphCanvas.prototype.processKey = function(e)\n{\n\tif(!this.graph)\n\t\treturn;\n\n\tvar block_default = false;\n\t//console.log(e); //debug\n\n\tif(e.target.localName == &quot;input&quot;)\n\t\treturn;\n\n\tif(e.type == &quot;keydown&quot;)\n\t{\n\t\tif(e.keyCode == 32) //esc\n\t\t{\n\t\t\tthis.dragging_canvas = true;\n\t\t\tblock_default = true;\n\t\t}\n\n\t\t//select all Control A\n\t\tif(e.keyCode == 65 &amp;&amp; e.ctrlKey)\n\t\t{\n\t\t\tthis.selectNodes();\n\t\t\tblock_default = true;\n\t\t}\n\n\t\tif(e.code == &quot;KeyC&quot; &amp;&amp; (e.metaKey || e.ctrlKey) &amp;&amp; !e.shiftKey ) //copy\n\t\t{\n\t\t\tif(this.selected_nodes)\n\t\t\t{\n\t\t\t\tthis.copyToClipboard();\n\t\t\t\tblock_default = true;\n\t\t\t}\n\t\t}\n\n\t\tif(e.code == &quot;KeyV&quot; &amp;&amp; (e.metaKey || e.ctrlKey) &amp;&amp; !e.shiftKey ) //paste\n\t\t{\n\t\t\tthis.pasteFromClipboard();\n\t\t}\n\n\t\t//delete or backspace\n\t\tif(e.keyCode == 46 || e.keyCode == 8)\n\t\t{\n\t\t\tif(e.target.localName != &quot;input&quot; &amp;&amp; e.target.localName != &quot;textarea&quot;)\n\t\t\t{\n\t\t\t\tthis.deleteSelectedNodes();\n\t\t\t\tblock_default = true;\n\t\t\t}\n\t\t}\n\n\t\t//collapse\n\t\t//...\n\n\t\t//TODO\n\t\tif(this.selected_nodes)\n\t\t\tfor (var i in this.selected_nodes)\n\t\t\t\tif(this.selected_nodes[i].onKeyDown)\n\t\t\t\t\tthis.selected_nodes[i].onKeyDown(e);\n\t}\n\telse if( e.type == &quot;keyup&quot; )\n\t{\n\t\tif(e.keyCode == 32)\n\t\t\tthis.dragging_canvas = false;\n\n\t\tif(this.selected_nodes)\n\t\t\tfor (var i in this.selected_nodes)\n\t\t\t\tif(this.selected_nodes[i].onKeyUp)\n\t\t\t\t\tthis.selected_nodes[i].onKeyUp(e);\n\t}\n\n\tthis.graph.change();\n\n\tif(block_default)\n\t{\n\t\te.preventDefault();\n\t\te.stopImmediatePropagation();\n\t\treturn false;\n\t}\n}\n\nLGraphCanvas.prototype.copyToClipboard = function()\n{\n\tvar clipboard_info = {\n\t\tnodes: [],\n\t\tlinks: []\n\t};\n\tvar index = 0;\n\tvar selected_nodes_array = [];\n\tfor(var i in this.selected_nodes)\n\t{\n\t\tvar node = this.selected_nodes[i];\n\t\tnode._relative_id = index;\n\t\tselected_nodes_array.push( node );\n\t\tindex += 1;\n\t}\n\n\tfor(var i = 0; i &lt; selected_nodes_array.length; ++i)\n\t{\n\t\tvar node = selected_nodes_array[i];\n\t\tclipboard_info.nodes.push( node.clone().serialize() );\n\t\tif(node.inputs &amp;&amp; node.inputs.length)\n\t\t\tfor(var j = 0; j &lt; node.inputs.length; ++j)\n\t\t\t{\n\t\t\t\tvar input = node.inputs[j];\n\t\t\t\tif(!input || input.link == null)\n\t\t\t\t\tcontinue;\n\t\t\t\tvar link_info = this.graph.links[ input.link ];\n\t\t\t\tif(!link_info)\n\t\t\t\t\tcontinue;\n\t\t\t\tvar target_node = this.graph.getNodeById( link_info.origin_id );\n\t\t\t\tif(!target_node || !this.selected_nodes[ target_node.id ] ) //improve this by allowing connections to non-selected nodes\n\t\t\t\t\tcontinue; //not selected\n\t\t\t\tclipboard_info.links.push([ target_node._relative_id, j, node._relative_id, link_info.target_slot ]);\n\t\t\t}\n\t}\n\tlocalStorage.setItem( &quot;litegrapheditor_clipboard&quot;, JSON.stringify( clipboard_info ) );\n}\n\nLGraphCanvas.prototype.pasteFromClipboard = function()\n{\n\tvar data = localStorage.getItem( &quot;litegrapheditor_clipboard&quot; );\n\tif(!data)\n\t\treturn;\n\n\t//create nodes\n\tvar clipboard_info = JSON.parse(data);\n\tvar nodes = [];\n\tfor(var i = 0; i &lt; clipboard_info.nodes.length; ++i)\n\t{\n\t\tvar node_data = clipboard_info.nodes[i];\n\t\tvar node = LiteGraph.createNode( node_data.type );\n\t\tif(node)\n\t\t{\n\t\t\tnode.configure(node_data);\n\t\t\tnode.pos[0] += 5;\n\t\t\tnode.pos[1] += 5;\n\t\t\tthis.graph.add( node );\n\t\t\tnodes.push( node );\n\t\t}\n\t}\n\n\t//create links\n\tfor(var i = 0; i &lt; clipboard_info.links.length; ++i)\n\t{\n\t\tvar link_info = clipboard_info.links[i];\n\t\tvar origin_node = nodes[ link_info[0] ];\n\t\tvar target_node = nodes[ link_info[2] ];\n\t\torigin_node.connect( link_info[1], target_node, link_info[3] );\n\t}\n\n\tthis.selectNodes( nodes );\n}\n\n/**\n* process a item drop event on top the canvas\n* @method processDrop\n**/\nLGraphCanvas.prototype.processDrop = function(e)\n{\n\te.preventDefault();\n\tthis.adjustMouseEvent(e);\n\n\n\tvar pos = [e.canvasX,e.canvasY];\n\tvar node = this.graph.getNodeOnPos(pos[0],pos[1]);\n\n\tif(!node)\n\t{\n\t\tvar r = null;\n\t\tif(this.onDropItem)\n\t\t\tr = this.onDropItem( event );\n\t\tif(!r)\n\t\t\tthis.checkDropItem(e);\n\t\treturn;\n\t}\n\n\tif( node.onDropFile || node.onDropData )\n\t{\n\t\tvar files = e.dataTransfer.files;\n\t\tif(files &amp;&amp; files.length)\n\t\t{\n\t\t\tfor(var i=0; i &lt; files.length; i++)\n\t\t\t{\n\t\t\t\tvar file = e.dataTransfer.files[0];\n\t\t\t\tvar filename = file.name;\n\t\t\t\tvar ext = LGraphCanvas.getFileExtension( filename );\n\t\t\t\t//console.log(file);\n\n\t\t\t\tif(node.onDropFile)\n\t\t\t\t\tnode.onDropFile(file);\n\n\t\t\t\tif(node.onDropData)\n\t\t\t\t{\n\t\t\t\t\t//prepare reader\n\t\t\t\t\tvar reader = new FileReader();\n\t\t\t\t\treader.onload = function (event) {\n\t\t\t\t\t\t//console.log(event.target);\n\t\t\t\t\t\tvar data = event.target.result;\n\t\t\t\t\t\tnode.onDropData( data, filename, file );\n\t\t\t\t\t};\n\n\t\t\t\t\t//read data\n\t\t\t\t\tvar type = file.type.split(&quot;/&quot;)[0];\n\t\t\t\t\tif(type == &quot;text&quot; || type == &quot;&quot;)\n\t\t\t\t\t\treader.readAsText(file);\n\t\t\t\t\telse if (type == &quot;image&quot;)\n\t\t\t\t\t\treader.readAsDataURL(file);\n\t\t\t\t\telse\n\t\t\t\t\t\treader.readAsArrayBuffer(file);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif(node.onDropItem)\n\t{\n\t\tif( node.onDropItem( event ) )\n\t\t\treturn true;\n\t}\n\n\tif(this.onDropItem)\n\t\treturn this.onDropItem( event );\n\n\treturn false;\n}\n\n//called if the graph doesnt have a default drop item behaviour\nLGraphCanvas.prototype.checkDropItem = function(e)\n{\n\tif(e.dataTransfer.files.length)\n\t{\n\t\tvar file = e.dataTransfer.files[0];\n\t\tvar ext = LGraphCanvas.getFileExtension( file.name ).toLowerCase();\n\t\tvar nodetype = LiteGraph.node_types_by_file_extension[ext];\n\t\tif(nodetype)\n\t\t{\n\t\t\tvar node = LiteGraph.createNode( nodetype.type );\n\t\t\tnode.pos = [e.canvasX, e.canvasY];\n\t\t\tthis.graph.add( node );\n\t\t\tif( node.onDropFile )\n\t\t\t\tnode.onDropFile( file );\n\t\t}\n\t}\n}\n\n\nLGraphCanvas.prototype.processNodeDblClicked = function(n)\n{\n\tif(this.onShowNodePanel)\n\t\tthis.onShowNodePanel(n);\n\n\tif(this.onNodeDblClicked)\n\t\tthis.onNodeDblClicked(n);\n\n\tthis.setDirty(true);\n}\n\nLGraphCanvas.prototype.processNodeSelected = function(node,e)\n{\n\tthis.selectNode( node, e &amp;&amp; e.shiftKey );\n\tif(this.onNodeSelected)\n\t\tthis.onNodeSelected(node);\n}\n\nLGraphCanvas.prototype.processNodeDeselected = function(node)\n{\n\tthis.deselectNode(node);\n\tif(this.onNodeDeselected)\n\t\tthis.onNodeDeselected(node);\n}\n\n/**\n* selects a given node (or adds it to the current selection)\n* @method selectNode\n**/\nLGraphCanvas.prototype.selectNode = function( node, add_to_current_selection )\n{\n\tif(node == null)\n\t\tthis.deselectAllNodes();\n\telse\n\t\tthis.selectNodes([node], add_to_current_selection );\n}\n\n/**\n* selects several nodes (or adds them to the current selection)\n* @method selectNodes\n**/\nLGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection )\n{\n\tif(!add_to_current_selection)\n\t\tthis.deselectAllNodes();\n\n\tnodes = nodes || this.graph._nodes;\n\tfor(var i = 0; i &lt; nodes.length; ++i)\n\t{\n\t\tvar node = nodes[i];\n\t\tif(node.is_selected)\n\t\t\tcontinue;\n\n\t\tif( !node.is_selected &amp;&amp; node.onSelected )\n\t\t\tnode.onSelected();\n\t\tnode.is_selected = true;\n\t\tthis.selected_nodes[ node.id ] = node;\n\n\t\tif(node.inputs)\n\t\t\tfor(var j = 0; j &lt; node.inputs.length; ++j)\n\t\t\t\tthis.highlighted_links[ node.inputs[j].link ] = true;\n\t\tif(node.outputs)\n\t\t\tfor(var j = 0; j &lt; node.outputs.length; ++j)\n\t\t\t{\n\t\t\t\tvar out = node.outputs[j];\n\t\t\t\tif( out.links )\n\t\t\t\t\tfor(var k = 0; k &lt; out.links.length; ++k)\n\t\t\t\t\t\tthis.highlighted_links[ out.links[k] ] = true;\n\t\t\t}\n\n\t}\n\n\tthis.setDirty(true);\n}\n\n/**\n* removes a node from the current selection\n* @method deselectNode\n**/\nLGraphCanvas.prototype.deselectNode = function( node )\n{\n\tif(!node.is_selected)\n\t\treturn;\n\tif(node.onDeselected)\n\t\tnode.onDeselected();\n\tnode.is_selected = false;\n\n\t//remove highlighted\n\tif(node.inputs)\n\t\tfor(var i = 0; i &lt; node.inputs.length; ++i)\n\t\t\tdelete this.highlighted_links[ node.inputs[i].link ];\n\tif(node.outputs)\n\t\tfor(var i = 0; i &lt; node.outputs.length; ++i)\n\t\t{\n\t\t\tvar out = node.outputs[i];\n\t\t\tif( out.links )\n\t\t\t\tfor(var j = 0; j &lt; out.links.length; ++j)\n\t\t\t\t\tdelete this.highlighted_links[ out.links[j] ];\n\t\t}\n}\n\n/**\n* removes all nodes from the current selection\n* @method deselectAllNodes\n**/\nLGraphCanvas.prototype.deselectAllNodes = function()\n{\n\tif(!this.graph)\n\t\treturn;\n\tvar nodes = this.graph._nodes;\n\tfor(var i = 0, l = nodes.length; i &lt; l; ++i)\n\t{\n\t\tvar node = nodes[i];\n\t\tif(!node.is_selected)\n\t\t\tcontinue;\n\t\tif(node.onDeselected)\n\t\t\tnode.onDeselected();\n\t\tnode.is_selected = false;\n\t}\n\tthis.selected_nodes = {};\n\tthis.highlighted_links = {};\n\tthis.setDirty(true);\n}\n\n/**\n* deletes all nodes in the current selection from the graph\n* @method deleteSelectedNodes\n**/\nLGraphCanvas.prototype.deleteSelectedNodes = function()\n{\n\tfor(var i in this.selected_nodes)\n\t{\n\t\tvar m = this.selected_nodes[i];\n\t\t//if(m == this.node_in_panel) this.showNodePanel(null);\n\t\tthis.graph.remove(m);\n\t}\n\tthis.selected_nodes = {};\n\tthis.highlighted_links = {};\n\tthis.setDirty(true);\n}\n\n/**\n* centers the camera on a given node\n* @method centerOnNode\n**/\nLGraphCanvas.prototype.centerOnNode = function(node)\n{\n\tthis.ds.offset[0] = -node.pos[0] - node.size[0] * 0.5 + (this.canvas.width * 0.5 / this.ds.scale);\n\tthis.ds.offset[1] = -node.pos[1] - node.size[1] * 0.5 + (this.canvas.height * 0.5 / this.ds.scale);\n\tthis.setDirty(true,true);\n}\n\n/**\n* adds some useful properties to a mouse event, like the position in graph coordinates\n* @method adjustMouseEvent\n**/\nLGraphCanvas.prototype.adjustMouseEvent = function(e)\n{\n\tif(this.canvas)\n\t{\n\t\tvar b = this.canvas.getBoundingClientRect();\n\t\te.localX = e.clientX - b.left;\n\t\te.localY = e.clientY - b.top;\n\t}\n\telse\n\t{\n\t\te.localX = e.clientX;\n\t\te.localY = e.clientY;\n\t}\n\n\te.deltaX = e.localX - this.last_mouse_position[0];\n\te.deltaY = e.localY - this.last_mouse_position[1];\n\n\tthis.last_mouse_position[0] = e.localX;\n\tthis.last_mouse_position[1] = e.localY;\n\n\te.canvasX = e.localX / this.ds.scale - this.ds.offset[0];\n\te.canvasY = e.localY / this.ds.scale - this.ds.offset[1];\n}\n\n/**\n* changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom\n* @method setZoom\n**/\nLGraphCanvas.prototype.setZoom = function(value, zooming_center)\n{\n\tthis.ds.changeScale( value, zooming_center);\n\t/*\n\tif(!zooming_center &amp;&amp; this.canvas)\n\t\tzooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5];\n\n\tvar center = this.convertOffsetToCanvas( zooming_center );\n\n\tthis.ds.scale = value;\n\n\tif(this.scale &gt; this.max_zoom)\n\t\tthis.scale = this.max_zoom;\n\telse if(this.scale &lt; this.min_zoom)\n\t\tthis.scale = this.min_zoom;\n\n\tvar new_center = this.convertOffsetToCanvas( zooming_center );\n\tvar delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];\n\n\tthis.offset[0] += delta_offset[0];\n\tthis.offset[1] += delta_offset[1];\n\t*/\n\n\tthis.dirty_canvas = true;\n\tthis.dirty_bgcanvas = true;\n}\n\n/**\n* converts a coordinate from graph coordinates to canvas2D coordinates\n* @method convertOffsetToCanvas\n**/\nLGraphCanvas.prototype.convertOffsetToCanvas = function( pos, out )\n{\n\treturn this.ds.convertOffsetToCanvas( pos, out );\n}\n\n/**\n* converts a coordinate from Canvas2D coordinates to graph space\n* @method convertCanvasToOffset\n**/\nLGraphCanvas.prototype.convertCanvasToOffset = function( pos, out )\n{\n\treturn this.ds.convertCanvasToOffset( pos, out );\n}\n\n//converts event coordinates from canvas2D to graph coordinates\nLGraphCanvas.prototype.convertEventToCanvasOffset = function(e)\n{\n\tvar rect = this.canvas.getBoundingClientRect();\n\treturn this.convertCanvasToOffset([e.clientX - rect.left,e.clientY - rect.top]);\n}\n\n/**\n* brings a node to front (above all other nodes)\n* @method bringToFront\n**/\nLGraphCanvas.prototype.bringToFront = function(node)\n{\n\tvar i = this.graph._nodes.indexOf(node);\n\tif(i == -1) return;\n\n\tthis.graph._nodes.splice(i,1);\n\tthis.graph._nodes.push(node);\n}\n\n/**\n* sends a node to the back (below all other nodes)\n* @method sendToBack\n**/\nLGraphCanvas.prototype.sendToBack = function(node)\n{\n\tvar i = this.graph._nodes.indexOf(node);\n\tif(i == -1) return;\n\n\tthis.graph._nodes.splice(i,1);\n\tthis.graph._nodes.unshift(node);\n}\n\n/* Interaction */\n\n\n\n/* LGraphCanvas render */\nvar temp = new Float32Array(4);\n\n/**\n* checks which nodes are visible (inside the camera area)\n* @method computeVisibleNodes\n**/\nLGraphCanvas.prototype.computeVisibleNodes = function( nodes, out )\n{\n\tvar visible_nodes = out || [];\n\tvisible_nodes.length = 0;\n\tnodes = nodes || this.graph._nodes;\n\tfor(var i = 0, l = nodes.length; i &lt; l; ++i)\n\t{\n\t\tvar n = nodes[i];\n\n\t\t//skip rendering nodes in live mode\n\t\tif( this.live_mode &amp;&amp; !n.onDrawBackground &amp;&amp; !n.onDrawForeground )\n\t\t\tcontinue;\n\n\t\tif(!overlapBounding( this.visible_area, n.getBounding( temp ) ))\n\t\t\tcontinue; //out of the visible area\n\n\t\tvisible_nodes.push(n);\n\t}\n\treturn visible_nodes;\n}\n\n/**\n* renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)\n* @method draw\n**/\nLGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas)\n{\n\tif(!this.canvas)\n\t\treturn;\n\n\t//fps counting\n\tvar now = LiteGraph.getTime();\n\tthis.render_time = (now - this.last_draw_time)*0.001;\n\tthis.last_draw_time = now;\n\n\tif(this.graph)\n\t\tthis.ds.computeVisibleArea();\n\n\tif(this.dirty_bgcanvas || force_bgcanvas || this.always_render_background || (this.graph &amp;&amp; this.graph._last_trigger_time &amp;&amp; (now - this.graph._last_trigger_time) &lt; 1000) )\n\t\tthis.drawBackCanvas();\n\n\tif(this.dirty_canvas || force_canvas)\n\t\tthis.drawFrontCanvas();\n\n\tthis.fps = this.render_time ? (1.0 / this.render_time) : 0;\n\tthis.frame += 1;\n}\n\n/**\n* draws the front canvas (the one containing all the nodes)\n* @method drawFrontCanvas\n**/\nLGraphCanvas.prototype.drawFrontCanvas = function()\n{\n\tthis.dirty_canvas = false;\n\n\tif(!this.ctx)\n\t\tthis.ctx = this.bgcanvas.getContext(&quot;2d&quot;);\n\tvar ctx = this.ctx;\n\tif(!ctx) //maybe is using webgl...\n\t\treturn;\n\n\tif(ctx.start2D)\n\t\tctx.start2D();\n\n\tvar canvas = this.canvas;\n\n\t//reset in case of error\n\tctx.restore();\n\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\n\t//clip dirty area if there is one, otherwise work in full canvas\n\tif(this.dirty_area)\n\t{\n\t\tctx.save();\n\t\tctx.beginPath();\n\t\tctx.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]);\n\t\tctx.clip();\n\t}\n\n\t//clear\n\t//canvas.width = canvas.width;\n\tif(this.clear_background)\n\t\tctx.clearRect(0,0,canvas.width, canvas.height);\n\n\t//draw bg canvas\n\tif(this.bgcanvas == this.canvas)\n\t\tthis.drawBackCanvas();\n\telse\n\t\tctx.drawImage(this.bgcanvas,0,0);\n\n\t//rendering\n\tif(this.onRender)\n\t\tthis.onRender(canvas, ctx);\n\n\t//info widget\n\tif(this.show_info)\n\t\tthis.renderInfo(ctx);\n\n\tif(this.graph)\n\t{\n\t\t//apply transformations\n\t\tctx.save();\n\t\tthis.ds.toCanvasContext( ctx );\n\n\t\t//draw nodes\n\t\tvar drawn_nodes = 0;\n\t\tvar visible_nodes = this.computeVisibleNodes( null, this.visible_nodes );\n\n\t\tfor (var i = 0; i &lt; visible_nodes.length; ++i)\n\t\t{\n\t\t\tvar node = visible_nodes[i];\n\n\t\t\t//transform coords system\n\t\t\tctx.save();\n\t\t\tctx.translate( node.pos[0], node.pos[1] );\n\n\t\t\t//Draw\n\t\t\tthis.drawNode( node, ctx );\n\t\t\tdrawn_nodes += 1;\n\n\t\t\t//Restore\n\t\t\tctx.restore();\n\t\t}\n\n\t\t//on top (debug)\n\t\tif( this.render_execution_order)\n\t\t\tthis.drawExecutionOrder(ctx);\n\n\n\t\t//connections ontop?\n\t\tif(this.graph.config.links_ontop)\n\t\t\tif(!this.live_mode)\n\t\t\t\tthis.drawConnections(ctx);\n\n\t\t//current connection (the one being dragged by the mouse)\n\t\tif(this.connecting_pos != null)\n\t\t{\n\t\t\tctx.lineWidth = this.connections_width;\n\t\t\tvar link_color = null;\n\n\t\t\tswitch( this.connecting_output.type )\n\t\t\t{\n\t\t\t\tcase LiteGraph.EVENT: link_color = LiteGraph.EVENT_LINK_COLOR; break;\n\t\t\t\tdefault:\n\t\t\t\t\tlink_color = LiteGraph.CONNECTING_LINK_COLOR;\n\t\t\t}\n\t\t\t\n\t\t\t//the connection being dragged by the mouse\n\t\t\tthis.renderLink( ctx, this.connecting_pos, [ this.canvas_mouse[0], this.canvas_mouse[1] ], null, false, null, link_color, this.connecting_output.dir || (this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT), LiteGraph.CENTER );\n\n\t\t\tctx.beginPath();\n\t\t\t\tif( this.connecting_output.type === LiteGraph.EVENT || this.connecting_output.shape === LiteGraph.BOX_SHAPE )\n\t\t\t\t\tctx.rect( (this.connecting_pos[0] - 6) + 0.5, (this.connecting_pos[1] - 5) + 0.5,14,10);\n\t\t\t\telse\n\t\t\t\t\tctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2);\n\t\t\tctx.fill();\n\n\t\t\tctx.fillStyle = &quot;#ffcc00&quot;;\n\t\t\tif(this._highlight_input)\n\t\t\t{\n\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.arc( this._highlight_input[0], this._highlight_input[1],6,0,Math.PI*2);\n\t\t\t\tctx.fill();\n\t\t\t}\n\t\t}\n\n\t\tif( this.dragging_rectangle )\n\t\t{\n\t\t\tctx.strokeStyle = &quot;#FFF&quot;;\n\t\t\tctx.strokeRect( this.dragging_rectangle[0], this.dragging_rectangle[1], this.dragging_rectangle[2], this.dragging_rectangle[3] );\n\t\t}\n\n\t\tif( this.onDrawForeground )\n\t\t\tthis.onDrawForeground( ctx, this.visible_rect );\n\n\t\tctx.restore();\n\t}\n\n\tif( this.onDrawOverlay )\n\t\tthis.onDrawOverlay( ctx );\n\n\tif(this.dirty_area)\n\t{\n\t\tctx.restore();\n\t\t//this.dirty_area = null;\n\t}\n\n\tif(ctx.finish2D) //this is a function I use in webgl renderer\n\t\tctx.finish2D();\n}\n\n/**\n* draws some useful stats in the corner of the canvas\n* @method renderInfo\n**/\nLGraphCanvas.prototype.renderInfo = function( ctx, x, y )\n{\n\tx = x || 0;\n\ty = y || 0;\n\n\tctx.save();\n\tctx.translate( x, y );\n\n\tctx.font = &quot;10px Arial&quot;;\n\tctx.fillStyle = &quot;#888&quot;;\n\tif(this.graph)\n\t{\n\t\tctx.fillText( &quot;T: &quot; + this.graph.globaltime.toFixed(2)+&quot;s&quot;,5,13*1 );\n\t\tctx.fillText( &quot;I: &quot; + this.graph.iteration,5,13*2 );\n\t\tctx.fillText( &quot;N: &quot; + this.graph._nodes.length + &quot; [&quot; + this.visible_nodes.length + &quot;]&quot;,5,13*3  );\n\t\tctx.fillText( &quot;V: &quot; + this.graph._version,5,13*4 );\n\t\tctx.fillText( &quot;FPS:&quot; + this.fps.toFixed(2),5,13*5 );\n\t}\n\telse\n\t\tctx.fillText( &quot;No graph selected&quot;,5,13*1 );\n\tctx.restore();\n}\n\n/**\n* draws the back canvas (the one containing the background and the connections)\n* @method drawBackCanvas\n**/\nLGraphCanvas.prototype.drawBackCanvas = function()\n{\n\tvar canvas = this.bgcanvas;\n\tif(canvas.width != this.canvas.width ||\n\t\tcanvas.height != this.canvas.height)\n\t{\n\t\tcanvas.width = this.canvas.width;\n\t\tcanvas.height = this.canvas.height;\n\t}\n\n\tif(!this.bgctx)\n\t\tthis.bgctx = this.bgcanvas.getContext(&quot;2d&quot;);\n\tvar ctx = this.bgctx;\n\tif(ctx.start)\n\t\tctx.start();\n\n\t//clear\n\tif(this.clear_background)\n\t\tctx.clearRect(0,0,canvas.width, canvas.height);\n\n\tif(this._graph_stack &amp;&amp; this._graph_stack.length)\n\t{\n\t\tctx.save();\n\t\tvar parent_graph = this._graph_stack[ this._graph_stack.length - 1];\n\t\tvar subgraph_node = this.graph._subgraph_node;\n\t\tctx.strokeStyle = subgraph_node.bgcolor;\n\t\tctx.lineWidth = 10;\n\t\tctx.strokeRect(1,1,canvas.width-2,canvas.height-2);\n\t\tctx.lineWidth = 1;\n\t\tctx.font = &quot;40px Arial&quot;\n\t\tctx.textAlign = &quot;center&quot;;\n\t\tctx.fillStyle = subgraph_node.bgcolor;\n\t\tvar title = &quot;&quot;;\n\t\tfor(var i = 1; i &lt; this._graph_stack.length; ++i)\n\t\t\ttitle += this._graph_stack[i]._subgraph_node.getTitle() + &quot; &gt;&gt; &quot;;\n\t\tctx.fillText( title + subgraph_node.getTitle(), canvas.width * 0.5, 40 );\n\t\tctx.restore();\n\t}\n\n\tvar bg_already_painted = false;\n\tif(this.onRenderBackground)\n\t\tbg_already_painted = this.onRenderBackground( canvas, ctx );\n\n\t//reset in case of error\n\tctx.restore();\n\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\tthis.visible_links.length = 0;\n\n\tif(this.graph)\n\t{\n\t\t//apply transformations\n\t\tctx.save();\n\t\tthis.ds.toCanvasContext(ctx);\n\n\t\t//render BG\n\t\tif(this.background_image &amp;&amp; this.ds.scale &gt; 0.5 &amp;&amp; !bg_already_painted)\n\t\t{\n\t\t\tif (this.zoom_modify_alpha)\n\t\t\t\tctx.globalAlpha = (1.0 - 0.5 / this.ds.scale) * this.editor_alpha;\n\t\t\telse\n\t\t\t\tctx.globalAlpha = this.editor_alpha;\n\t\t\tctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false;\n\t\t\tif(!this._bg_img || this._bg_img.name != this.background_image)\n\t\t\t{\n\t\t\t\tthis._bg_img = new Image();\n\t\t\t\tthis._bg_img.name = this.background_image;\n\t\t\t\tthis._bg_img.src = this.background_image;\n\t\t\t\tvar that = this;\n\t\t\t\tthis._bg_img.onload = function() {\n\t\t\t\t\tthat.draw(true,true);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar pattern = null;\n\t\t\tif(this._pattern == null &amp;&amp; this._bg_img.width &gt; 0)\n\t\t\t{\n\t\t\t\tpattern = ctx.createPattern( this._bg_img, &#x27;repeat&#x27; );\n\t\t\t\tthis._pattern_img = this._bg_img;\n\t\t\t\tthis._pattern = pattern;\n\t\t\t}\n\t\t\telse\n\t\t\t\tpattern = this._pattern;\n\t\t\tif(pattern)\n\t\t\t{\n\t\t\t\tctx.fillStyle = pattern;\n\t\t\t\tctx.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3]);\n\t\t\t\tctx.fillStyle = &quot;transparent&quot;;\n\t\t\t}\n\n\t\t\tctx.globalAlpha = 1.0;\n\t\t\tctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true;\n\t\t}\n\n\t\t//groups\n\t\tif(this.graph._groups.length &amp;&amp; !this.live_mode)\n\t\t\tthis.drawGroups(canvas, ctx);\n\n\t\tif( this.onDrawBackground )\n\t\t\tthis.onDrawBackground( ctx, this.visible_area );\n\t\tif( this.onBackgroundRender ) //LEGACY\n\t\t{\n\t\t\tconsole.error(&quot;WARNING! onBackgroundRender deprecated, now is named onDrawBackground &quot;);\n\t\t\tthis.onBackgroundRender = null;\n\t\t}\n\n\t\t//DEBUG: show clipping area\n\t\t//ctx.fillStyle = &quot;red&quot;;\n\t\t//ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20);\n\n\t\t//bg\n\t\tif (this.render_canvas_border) {\n\t\t\tctx.strokeStyle = &quot;#235&quot;;\n\t\t\tctx.strokeRect(0,0,canvas.width,canvas.height);\n\t\t}\n\n\t\tif(this.render_connections_shadows)\n\t\t{\n\t\t\tctx.shadowColor = &quot;#000&quot;;\n\t\t\tctx.shadowOffsetX = 0;\n\t\t\tctx.shadowOffsetY = 0;\n\t\t\tctx.shadowBlur = 6;\n\t\t}\n\t\telse\n\t\t\tctx.shadowColor = &quot;rgba(0,0,0,0)&quot;;\n\n\t\t//draw connections\n\t\tif(!this.live_mode)\n\t\t\tthis.drawConnections(ctx);\n\n\t\tctx.shadowColor = &quot;rgba(0,0,0,0)&quot;;\n\n\t\t//restore state\n\t\tctx.restore();\n\t}\n\n\tif(ctx.finish)\n\t\tctx.finish();\n\n\tthis.dirty_bgcanvas = false;\n\tthis.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas\n}\n\nvar temp_vec2 = new Float32Array(2);\n\n/**\n* draws the given node inside the canvas\n* @method drawNode\n**/\nLGraphCanvas.prototype.drawNode = function(node, ctx )\n{\n\tvar glow = false;\n\tthis.current_node = node;\n\n\tvar color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR;\n\tvar bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;\n\n\t//shadow and glow\n\tif (node.mouseOver)\n\t\tglow = true;\n\n\t//only render if it forces it to do it\n\tif(this.live_mode)\n\t{\n\t\tif(!node.flags.collapsed)\n\t\t{\n\t\t\tctx.shadowColor = &quot;transparent&quot;;\n\t\t\tif(node.onDrawForeground)\n\t\t\t\tnode.onDrawForeground(ctx, this, this.canvas );\n\t\t}\n\t\treturn;\n\t}\n\n\tvar editor_alpha = this.editor_alpha;\n\tctx.globalAlpha = editor_alpha;\n\n\tif(this.render_shadows)\n\t{\n\t\tctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;\n\t\tctx.shadowOffsetX = 2 * this.ds.scale;\n\t\tctx.shadowOffsetY = 2 * this.ds.scale;\n\t\tctx.shadowBlur = 3 * this.ds.scale;\n\t}\n\telse\n\t\tctx.shadowColor = &quot;transparent&quot;;\n\n\t//custom draw collapsed method (draw after shadows because they are affected)\n\tif(node.flags.collapsed &amp;&amp; node.onDrawCollaped &amp;&amp; node.onDrawCollapsed(ctx, this) == true)\n\t\treturn;\n\n\t//clip if required (mask)\n\tvar shape = node._shape || LiteGraph.BOX_SHAPE;\n\tvar size = temp_vec2;\n\ttemp_vec2.set( node.size );\n\tvar horizontal = node.horizontal;// || node.flags.horizontal;\n\n\tif( node.flags.collapsed )\n\t{\n\t\tctx.font = this.inner_text_font;\n\t\tvar title = node.getTitle ? node.getTitle() : node.title;\n\t\tif(title != null)\n\t\t{\n\t\t\tnode._collapsed_width = Math.min( node.size[0], ctx.measureText(title).width + LiteGraph.NODE_TITLE_HEIGHT * 2 );//LiteGraph.NODE_COLLAPSED_WIDTH;\n\t\t\tsize[0] = node._collapsed_width;\n\t\t\tsize[1] = 0;\n\t\t}\n\t}\n\t\n\tif( node.clip_area ) //Start clipping\n\t{\n\t\tctx.save();\n\t\tctx.beginPath();\n\t\tif(shape == LiteGraph.BOX_SHAPE)\n\t\t\tctx.rect(0,0,size[0], size[1]);\n\t\telse if (shape == LiteGraph.ROUND_SHAPE)\n\t\t\tctx.roundRect(0,0,size[0], size[1],10);\n\t\telse if (shape == LiteGraph.CIRCLE_SHAPE)\n\t\t\tctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2);\n\t\tctx.clip();\n\t}\n\n\t//draw shape\n\tif( node.has_errors )\n\t\tbgcolor = &quot;red&quot;;\n\tthis.drawNodeShape( node, ctx, size, color, bgcolor, node.is_selected, node.mouseOver );\n\tctx.shadowColor = &quot;transparent&quot;;\n\n\t//draw foreground\n\tif(node.onDrawForeground)\n\t\tnode.onDrawForeground( ctx, this, this.canvas );\n\n\t//connection slots\n\tctx.textAlign = horizontal ? &quot;center&quot; : &quot;left&quot;;\n\tctx.font = this.inner_text_font;\n\n\tvar render_text = this.ds.scale &gt; 0.6;\n\n\tvar out_slot = this.connecting_output;\n\tctx.lineWidth = 1;\n\n\tvar max_y = 0;\n\tvar slot_pos = new Float32Array(2); //to reuse\n\n\t//render inputs and outputs\n\tif(!node.flags.collapsed)\n\t{\n\t\t//input connection slots\n\t\tif(node.inputs)\n\t\t\tfor(var i = 0; i &lt; node.inputs.length; i++)\n\t\t\t{\n\t\t\t\tvar slot = node.inputs[i];\n\n\t\t\t\tctx.globalAlpha = editor_alpha;\n\t\t\t\t//change opacity of incompatible slots when dragging a connection\n\t\t\t\tif ( this.connecting_node &amp;&amp; LiteGraph.isValidConnection( slot.type &amp;&amp; out_slot.type ) )\n\t\t\t\t\tctx.globalAlpha = 0.4 * editor_alpha;\n\n\t\t\t\tctx.fillStyle = slot.link != null ? (slot.color_on || this.default_connection_color.input_on) : (slot.color_off || this.default_connection_color.input_off);\n\n\t\t\t\tvar pos = node.getConnectionPos( true, i, slot_pos );\n\t\t\t\tpos[0] -= node.pos[0];\n\t\t\t\tpos[1] -= node.pos[1];\n\t\t\t\tif( max_y &lt; pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5 )\n\t\t\t\t\tmax_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5;\n\n\t\t\t\tctx.beginPath();\n\n\t\t\t\tif (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE)\n\t\t\t\t{\n\t\t\t\t\tif (horizontal)\n\t                    ctx.rect((pos[0] - 5) + 0.5, (pos[1] - 8) + 0.5, 10, 14);\n\t\t\t\t\telse\n\t                    ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5, 14, 10);\n                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(pos[0] + 8, pos[1] + 0.5);\n                    ctx.lineTo(pos[0] - 4, (pos[1] + 6) + 0.5);\n                    ctx.lineTo(pos[0] - 4, (pos[1] - 6) + 0.5);\n                    ctx.closePath();\n                } else {\n                    ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);\n                }\n\n\t\t\t\tctx.fill();\n\n\t\t\t\t//render name\n\t\t\t\tif(render_text)\n\t\t\t\t{\n\t\t\t\t\tvar text = slot.label != null ? slot.label : slot.name;\n\t\t\t\t\tif(text)\n\t\t\t\t\t{\n\t\t\t\t\t\tctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;\n\t\t\t\t\t\tif( horizontal || slot.dir == LiteGraph.UP )\n\t\t\t\t\t\t\tctx.fillText(text,pos[0],pos[1] - 10);\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tctx.fillText(text,pos[0] + 10,pos[1] + 5);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t//output connection slots\n\t\tif(this.connecting_node)\n\t\t\tctx.globalAlpha = 0.4 * editor_alpha;\n\n\t\tctx.textAlign = horizontal ? &quot;center&quot; : &quot;right&quot;;\n\t\tctx.strokeStyle = &quot;black&quot;;\n\t\tif(node.outputs)\n\t\t\tfor(var i = 0; i &lt; node.outputs.length; i++)\n\t\t\t{\n\t\t\t\tvar slot = node.outputs[i];\n\n\t\t\t\tvar pos = node.getConnectionPos(false,i, slot_pos );\n\t\t\t\tpos[0] -= node.pos[0];\n\t\t\t\tpos[1] -= node.pos[1];\n\t\t\t\tif( max_y &lt; pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5)\n\t\t\t\t\tmax_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT*0.5;\n\n\t\t\t\tctx.fillStyle = slot.links &amp;&amp; slot.links.length ? (slot.color_on || this.default_connection_color.output_on) : (slot.color_off || this.default_connection_color.output_off);\n\t\t\t\tctx.beginPath();\n\t\t\t\t//ctx.rect( node.size[0] - 14,i*14,10,10);\n\n\t\t\t\tif (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE)\n\t\t\t\t{\n\t\t\t\t\tif( horizontal )\n\t\t\t\t\t\tctx.rect((pos[0] - 5) + 0.5,(pos[1] - 8) + 0.5,10,14);\n\t\t\t\t\telse\n\t\t\t\t\t\tctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10);\n                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(pos[0] + 8, pos[1] + 0.5);\n                    ctx.lineTo(pos[0] - 4, (pos[1] + 6) + 0.5);\n                    ctx.lineTo(pos[0] - 4, (pos[1] - 6) + 0.5);\n                    ctx.closePath();\n                } else {\n                    ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);\n                }\n\n\t\t\t\t//trigger\n\t\t\t\t//if(slot.node_id != null &amp;&amp; slot.slot == -1)\n\t\t\t\t//\tctx.fillStyle = &quot;#F85&quot;;\n\n\t\t\t\t//if(slot.links != null &amp;&amp; slot.links.length)\n\t\t\t\tctx.fill();\n\t\t\t\tctx.stroke();\n\n\t\t\t\t//render output name\n\t\t\t\tif(render_text)\n\t\t\t\t{\n\t\t\t\t\tvar text = slot.label != null ? slot.label : slot.name;\n\t\t\t\t\tif(text)\n\t\t\t\t\t{\n\t\t\t\t\t\tctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;\n\t\t\t\t\t\tif( horizontal || slot.dir == LiteGraph.DOWN )\n\t\t\t\t\t\t\tctx.fillText(text,pos[0],pos[1] - 8);\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tctx.fillText(text, pos[0] - 10,pos[1] + 5);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\tctx.textAlign = &quot;left&quot;;\n\t\tctx.globalAlpha = 1;\n\n\t\tif(node.widgets)\n\t\t{\n\t\t\tif( horizontal || node.widgets_up  )\n\t\t\t\tmax_y = 2;\n\t\t\tthis.drawNodeWidgets( node, max_y, ctx, (this.node_widget &amp;&amp; this.node_widget[0] == node) ? this.node_widget[1] : null );\n\t\t}\n\t}\n\telse if(this.render_collapsed_slots)//if collapsed\n\t{\n\t\tvar input_slot = null;\n\t\tvar output_slot = null;\n\n\t\t//get first connected slot to render\n\t\tif(node.inputs)\n\t\t{\n\t\t\tfor(var i = 0; i &lt; node.inputs.length; i++)\n\t\t\t{\n\t\t\t\tvar slot = node.inputs[i];\n\t\t\t\tif( slot.link == null )\n\t\t\t\t\tcontinue;\n\t\t\t\tinput_slot = slot;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif(node.outputs)\n\t\t{\n\t\t\tfor(var i = 0; i &lt; node.outputs.length; i++)\n\t\t\t{\n\t\t\t\tvar slot = node.outputs[i];\n\t\t\t\tif(!slot.links || !slot.links.length)\n\t\t\t\t\tcontinue;\n\t\t\t\toutput_slot = slot;\n\t\t\t}\n\t\t}\n\n\t\tif(input_slot)\n\t\t{\n\t\t\tvar x = 0;\n\t\t\tvar y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center\n\t\t\tif( horizontal )\n\t\t\t{\n\t\t\t\tx = node._collapsed_width * 0.5;\n\t\t\t\ty = -LiteGraph.NODE_TITLE_HEIGHT;\t\t\n\t\t\t}\n\t\t\tctx.fillStyle = &quot;#686&quot;;\n\t\t\tctx.beginPath();\n\t\t\tif ( slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) {\n\t\t\t\tctx.rect(x - 7 + 0.5, y-4,14,8);\n\t\t\t} else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n\t\t\t\tctx.moveTo(x + 8, y);\n\t\t\t\tctx.lineTo(x + -4, y - 4);\n\t\t\t\tctx.lineTo(x + -4, y + 4);\n\t\t\t\tctx.closePath();\n\t\t\t} else {\n\t\t\t\tctx.arc(x, y, 4, 0, Math.PI * 2);\n\t\t\t}\n\t\t\tctx.fill();\n\t\t}\n\n\t\tif(output_slot)\n\t\t{\n\t\t\tvar x = node._collapsed_width;\n\t\t\tvar y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center\n\t\t\tif( horizontal )\n\t\t\t{\n\t\t\t\tx = node._collapsed_width * 0.5;\n\t\t\t\ty = 0;\n\t\t\t}\n\t\t\tctx.fillStyle = &quot;#686&quot;;\n\t\t\tctx.strokeStyle = &quot;black&quot;;\n\t\t\tctx.beginPath();\n\t\t\tif (slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE) {\n\t\t\t\tctx.rect( x - 7 + 0.5, y - 4,14,8);\n\t\t\t} else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n\t\t\t\tctx.moveTo(x + 6, y);\n\t\t\t\tctx.lineTo(x - 6, y - 4);\n\t\t\t\tctx.lineTo(x - 6, y + 4);\n\t\t\t\tctx.closePath();\n\t\t\t} else {\n\t\t\t\tctx.arc(x, y, 4, 0, Math.PI * 2);\n\t\t\t}\n\t\t\tctx.fill();\n\t\t\t//ctx.stroke();\n\t\t}\n\t}\n\n\tif(node.clip_area)\n\t\tctx.restore();\n\n\tctx.globalAlpha = 1.0;\n}\n\n/**\n* draws the shape of the given node in the canvas\n* @method drawNodeShape\n**/\nvar tmp_area = new Float32Array(4);\n\nLGraphCanvas.prototype.drawNodeShape = function( node, ctx, size, fgcolor, bgcolor, selected, mouse_over )\n{\n\t//bg rect\n\tctx.strokeStyle = fgcolor;\n\tctx.fillStyle = bgcolor;\n\n\tvar title_height = LiteGraph.NODE_TITLE_HEIGHT;\n\tvar low_quality = this.ds.scale &lt; 0.5;\n\n\t//render node area depending on shape\n\tvar shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;\n\n\tvar title_mode = node.constructor.title_mode;\n\n\tvar render_title = true;\n\tif( title_mode == LiteGraph.TRANSPARENT_TITLE )\n\t\trender_title = false;\n\telse if( title_mode == LiteGraph.AUTOHIDE_TITLE &amp;&amp; mouse_over)\n\t\trender_title = true;\n\n\tvar area = tmp_area;\n\tarea[0] = 0; //x\n\tarea[1] = render_title ? -title_height : 0; //y\n\tarea[2] = size[0]+1; //w\n\tarea[3] = render_title ? size[1] + title_height : size[1]; //h\n\n\tvar old_alpha = ctx.globalAlpha;\n\n\t//full node shape\n\t//if(node.flags.collapsed)\n\t{\n\t\tctx.beginPath();\n\t\tif(shape == LiteGraph.BOX_SHAPE || low_quality )\n\t\t\tctx.fillRect( area[0], area[1], area[2], area[3] );\n\t\telse if (shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE)\n\t\t\tctx.roundRect( area[0], area[1], area[2], area[3], this.round_radius, shape == LiteGraph.CARD_SHAPE ? 0 : this.round_radius);\n\t\telse if (shape == LiteGraph.CIRCLE_SHAPE)\n\t\t\tctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2);\n\t\tctx.fill();\n\n\t\tctx.shadowColor = &quot;transparent&quot;;\n\t\tctx.fillStyle = &quot;rgba(0,0,0,0.2)&quot;;\n\t\tctx.fillRect(0,-1, area[2],2);\n\t}\n\tctx.shadowColor = &quot;transparent&quot;;\n\n\tif( node.onDrawBackground )\n\t\tnode.onDrawBackground( ctx, this, this.canvas );\n\n\t//title bg (remember, it is rendered ABOVE the node)\n\tif( render_title || title_mode == LiteGraph.TRANSPARENT_TITLE )\n\t{\n\t\t//title bar\n\t\tif(node.onDrawTitleBar)\n\t\t{\n\t\t\tnode.onDrawTitleBar(ctx, title_height, size, this.ds.scale, fgcolor);\n\t\t}\n\t\telse if(title_mode != LiteGraph.TRANSPARENT_TITLE &amp;&amp; (node.constructor.title_color || this.render_title_colored ))\n\t\t{\n\t\t\tvar title_color = node.constructor.title_color || fgcolor;\n\n\t\t\tif(node.flags.collapsed)\n\t\t\t\tctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;\n\t\n\t\t\t//* gradient test\n\t\t\tif(this.use_gradients)\n\t\t\t{\n\t\t\t\tvar grad = LGraphCanvas.gradients[ title_color ];\n\t\t\t\tif(!grad)\n\t\t\t\t{\n\t\t\t\t\tgrad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0,0,400,0);\n\t\t\t\t\tgrad.addColorStop(0, title_color);\n\t\t\t\t\tgrad.addColorStop(1, &quot;#000&quot;);\n\t\t\t\t}\n\t\t\t\tctx.fillStyle = grad;\n\t\t\t}\n\t\t\telse\n\t\t\t\tctx.fillStyle = title_color;\n\n\t\t\t//ctx.globalAlpha = 0.5 * old_alpha;\n\t\t\tctx.beginPath();\n\t\t\tif( shape == LiteGraph.BOX_SHAPE || low_quality )\n\t\t\t\tctx.rect(0, -title_height, size[0]+1, title_height);\n\t\t\telse if ( shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE )\n\t\t\t\tctx.roundRect(0,-title_height,size[0]+1, title_height, this.round_radius, node.flags.collapsed ? this.round_radius : 0);\n\t\t\tctx.fill();\n\t\t\tctx.shadowColor = &quot;transparent&quot;;\n\t\t}\n\n\t\t//title box\n\t\tvar box_size = 10;\n\t\tif(node.onDrawTitleBox)\n\t\t{\n\t\t\tnode.onDrawTitleBox( ctx, title_height, size, this.ds.scale );\n\t\t}\n\t\telse if ( shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CIRCLE_SHAPE || shape == LiteGraph.CARD_SHAPE )\n\t\t{\n\t\t\tif( low_quality )\n\t\t\t{\n\t\t\t\tctx.fillStyle = &quot;black&quot;;\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc(title_height * 0.5, title_height * -0.5, box_size*0.5+1,0,Math.PI*2);\n\t\t\t\tctx.fill();\n\t\t\t}\n\n\t\t\tctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;\n\t\t\tctx.beginPath();\n\t\t\tctx.arc(title_height * 0.5, title_height * -0.5, box_size*0.5,0,Math.PI*2);\n\t\t\tctx.fill();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( low_quality )\n\t\t\t{\n\t\t\t\tctx.fillStyle = &quot;black&quot;;\n\t\t\t\tctx.fillRect( (title_height - box_size) * 0.5 - 1, (title_height + box_size ) * -0.5 - 1, box_size + 2, box_size + 2);\n\t\t\t}\n\t\t\tctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;\n\t\t\tctx.fillRect( (title_height - box_size) * 0.5, (title_height + box_size ) * -0.5, box_size, box_size );\n\t\t}\n\t\tctx.globalAlpha = old_alpha;\n\n\t\t//title text\n\t\tif(node.onDrawTitleText)\n\t\t{\n\t\t\tnode.onDrawTitleText( ctx, title_height, size, this.ds.scale, this.title_text_font, selected);\n\t\t}\n\t\tif( !low_quality )\n\t\t{\n\t\t\tctx.font = this.title_text_font;\n\t\t\tvar title = node.getTitle();\n\t\t\tif(title)\n\t\t\t{\n\t\t\t\tif(selected)\n\t\t\t\t\tctx.fillStyle = &quot;white&quot;;\n\t\t\t\telse\n\t\t\t\t\tctx.fillStyle = node.constructor.title_text_color || this.node_title_color;\n\t\t\t\tif( node.flags.collapsed )\n\t\t\t\t{\n\t\t\t\t\tctx.textAlign =  &quot;center&quot;;\n\t\t\t\t\tvar measure = ctx.measureText(title);\n\t\t\t\t\tctx.fillText( title, title_height + measure.width * 0.5, LiteGraph.NODE_TITLE_TEXT_Y - title_height );\n\t\t\t\t\tctx.textAlign =  &quot;left&quot;;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tctx.textAlign =  &quot;left&quot;;\n\t\t\t\t\tctx.fillText( title, title_height, LiteGraph.NODE_TITLE_TEXT_Y - title_height );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif(node.onDrawTitle)\n\t\t\tnode.onDrawTitle(ctx);\n\t}\n\n\t//render selection marker\n\tif(selected)\n\t{\n\t\tif( node.onBounding )\n\t\t\tnode.onBounding( area );\n\n\t\tif( title_mode == LiteGraph.TRANSPARENT_TITLE )\n\t\t{\n\t\t\tarea[1] -= title_height;\n\t\t\tarea[3] += title_height;\n\t\t}\n\t\tctx.lineWidth = 1;\n\t\tctx.globalAlpha = 0.8;\n\t\tctx.beginPath();\n\t\tif( shape == LiteGraph.BOX_SHAPE )\n\t\t\tctx.rect(-6 + area[0],-6 + area[1], 12 + area[2], 12 + area[3] );\n\t\telse if (shape == LiteGraph.ROUND_SHAPE || (shape == LiteGraph.CARD_SHAPE &amp;&amp; node.flags.collapsed) )\n\t\t\tctx.roundRect(-6 + area[0],-6 + area[1], 12 + area[2], 12 + area[3] , this.round_radius * 2);\n\t\telse if (shape == LiteGraph.CARD_SHAPE)\n\t\t\tctx.roundRect(-6 + area[0],-6 + area[1], 12 + area[2], 12 + area[3] , this.round_radius * 2, 2);\n\t\telse if (shape == LiteGraph.CIRCLE_SHAPE)\n\t\t\tctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI*2);\n\t\tctx.strokeStyle = &quot;#FFF&quot;;\n\t\tctx.stroke();\n\t\tctx.strokeStyle = fgcolor;\n\t\tctx.globalAlpha = 1;\n\t}\n}\n\nvar margin_area = new Float32Array(4);\nvar link_bounding = new Float32Array(4);\nvar tempA = new Float32Array(2);\nvar tempB = new Float32Array(2);\n\n/**\n* draws every connection visible in the canvas\n* OPTIMIZE THIS: precatch connections position instead of recomputing them every time\n* @method drawConnections\n**/\nLGraphCanvas.prototype.drawConnections = function(ctx)\n{\n\tvar now = LiteGraph.getTime();\n\tvar visible_area = this.visible_area;\n\tmargin_area[0] = visible_area[0] - 20; margin_area[1] = visible_area[1] - 20; margin_area[2] = visible_area[2] + 40; margin_area[3] = visible_area[3] + 40;\n\n\t//draw connections\n\tctx.lineWidth = this.connections_width;\n\n\tctx.fillStyle = &quot;#AAA&quot;;\n\tctx.strokeStyle = &quot;#AAA&quot;;\n\tctx.globalAlpha = this.editor_alpha;\n\t//for every node\n\tvar nodes = this.graph._nodes;\n\tfor (var n = 0, l = nodes.length; n &lt; l; ++n)\n\t{\n\t\tvar node = nodes[n];\n\t\t//for every input (we render just inputs because it is easier as every slot can only have one input)\n\t\tif(!node.inputs || !node.inputs.length)\n\t\t\tcontinue;\n\t\n\t\tfor(var i = 0; i &lt; node.inputs.length; ++i)\n\t\t{\n\t\t\tvar input = node.inputs[i];\n\t\t\tif(!input || input.link == null)\n\t\t\t\tcontinue;\n\t\t\tvar link_id = input.link;\n\t\t\tvar link = this.graph.links[ link_id ];\n\t\t\tif(!link)\n\t\t\t\tcontinue;\n\n\t\t\t//find link info\n\t\t\tvar start_node = this.graph.getNodeById( link.origin_id );\n\t\t\tif(start_node == null) continue;\n\t\t\tvar start_node_slot = link.origin_slot;\n\t\t\tvar start_node_slotpos = null;\n\t\t\tif(start_node_slot == -1)\n\t\t\t\tstart_node_slotpos = [start_node.pos[0] + 10, start_node.pos[1] + 10];\n\t\t\telse\n\t\t\t\tstart_node_slotpos = start_node.getConnectionPos( false, start_node_slot, tempA );\n\t\t\tvar end_node_slotpos = node.getConnectionPos( true, i, tempB );\n\n\t\t\t//compute link bounding\n\t\t\tlink_bounding[0] = start_node_slotpos[0];\n\t\t\tlink_bounding[1] = start_node_slotpos[1];\n\t\t\tlink_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0];\n\t\t\tlink_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1];\n\t\t\tif( link_bounding[2] &lt; 0 ){\n\t\t\t\tlink_bounding[0] += link_bounding[2];\n\t\t\t\tlink_bounding[2] = Math.abs( link_bounding[2] );\n\t\t\t}\n\t\t\tif( link_bounding[3] &lt; 0 ){\n\t\t\t\tlink_bounding[1] += link_bounding[3];\n\t\t\t\tlink_bounding[3] = Math.abs( link_bounding[3] );\n\t\t\t}\n\n\t\t\t//skip links outside of the visible area of the canvas\n\t\t\tif( !overlapBounding( link_bounding, margin_area ) )\n\t\t\t\tcontinue;\n\n\t\t\tvar start_slot = start_node.outputs[ start_node_slot ];\n\t\t\tvar end_slot = node.inputs[i];\n\t\t\tif(!start_slot || !end_slot) continue;\n\t\t\tvar start_dir = start_slot.dir || (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT);\n\t\t\tvar end_dir = end_slot.dir || (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT);\n\n\t\t\tthis.renderLink( ctx, start_node_slotpos, end_node_slotpos, link, false, 0, null, start_dir, end_dir );\n\n\t\t\t//event triggered rendered on top\n\t\t\tif(link &amp;&amp; link._last_time &amp;&amp; (now - link._last_time) &lt; 1000 )\n\t\t\t{\n\t\t\t\tvar f = 2.0 - (now - link._last_time) * 0.002;\n\t\t\t\tvar tmp = ctx.globalAlpha;\n\t\t\t\tctx.globalAlpha = tmp * f;\n\t\t\t\tthis.renderLink( ctx, start_node_slotpos, end_node_slotpos, link, true, f, &quot;white&quot;, start_dir, end_dir );\n\t\t\t\tctx.globalAlpha = tmp;\n\t\t\t}\n\t\t}\n\t}\n\tctx.globalAlpha = 1;\n}\n\n/**\n* draws a link between two points\n* @method renderLink\n* @param {vec2} a start pos\n* @param {vec2} b end pos\n* @param {Object} link the link object with all the link info\n* @param {boolean} skip_border ignore the shadow of the link\n* @param {boolean} flow show flow animation (for events)\n* @param {string} color the color for the link\n* @param {number} start_dir the direction enum \n* @param {number} end_dir the direction enum \n* @param {number} num_sublines number of sublines (useful to represent vec3 or rgb)\n**/\nLGraphCanvas.prototype.renderLink = function( ctx, a, b, link, skip_border, flow, color, start_dir, end_dir, num_sublines )\n{\n\tif(link)\n\t\tthis.visible_links.push( link );\n\n\t//choose color\n\tif( !color &amp;&amp; link )\n\t\tcolor = link.color || LGraphCanvas.link_type_colors[ link.type ];\n\tif( !color )\n\t\tcolor = this.default_link_color;\n\tif( link != null &amp;&amp; this.highlighted_links[ link.id ] )\n\t\tcolor = &quot;#FFF&quot;;\n\n\tstart_dir = start_dir || LiteGraph.RIGHT;\n\tend_dir = end_dir || LiteGraph.LEFT;\n\n\tvar dist = distance(a,b);\n\n\tif(this.render_connections_border &amp;&amp; this.ds.scale &gt; 0.6)\n\t\tctx.lineWidth = this.connections_width + 4;\n\tctx.lineJoin = &quot;round&quot;;\n\tnum_sublines = num_sublines || 1;\n\tif(num_sublines &gt; 1)\n\t\tctx.lineWidth = 0.5;\n\n\t//begin line shape\n\tctx.beginPath();\n\tfor(var i = 0; i &lt; num_sublines; i += 1)\n\t{\n\t\tvar offsety = (i - (num_sublines-1)*0.5)*5;\n\n\t\tif(this.links_render_mode == LiteGraph.SPLINE_LINK)\n\t\t{\n\t\t\tctx.moveTo(a[0],a[1] + offsety);\n\t\t\tvar start_offset_x = 0;\n\t\t\tvar start_offset_y = 0;\n\t\t\tvar end_offset_x = 0;\n\t\t\tvar end_offset_y = 0;\n\t\t\tswitch(start_dir)\n\t\t\t{\n\t\t\t\tcase LiteGraph.LEFT: start_offset_x = dist*-0.25; break;\n\t\t\t\tcase LiteGraph.RIGHT: start_offset_x = dist*0.25; break;\n\t\t\t\tcase LiteGraph.UP: start_offset_y = dist*-0.25; break;\n\t\t\t\tcase LiteGraph.DOWN: start_offset_y = dist*0.25; break;\n\t\t\t}\n\t\t\tswitch(end_dir)\n\t\t\t{\n\t\t\t\tcase LiteGraph.LEFT: end_offset_x = dist*-0.25; break;\n\t\t\t\tcase LiteGraph.RIGHT: end_offset_x = dist*0.25; break;\n\t\t\t\tcase LiteGraph.UP: end_offset_y = dist*-0.25; break;\n\t\t\t\tcase LiteGraph.DOWN: end_offset_y = dist*0.25; break;\n\t\t\t}\n\t\t\tctx.bezierCurveTo(a[0] + start_offset_x, a[1] + start_offset_y + offsety,\n\t\t\t\t\t\t\t\tb[0] + end_offset_x , b[1] + end_offset_y + offsety,\n\t\t\t\t\t\t\t\tb[0], b[1] + offsety);\n\t\t}\n\t\telse if(this.links_render_mode == LiteGraph.LINEAR_LINK)\n\t\t{\n\t\t\tctx.moveTo(a[0],a[1] + offsety);\n\t\t\tvar start_offset_x = 0;\n\t\t\tvar start_offset_y = 0;\n\t\t\tvar end_offset_x = 0;\n\t\t\tvar end_offset_y = 0;\n\t\t\tswitch(start_dir)\n\t\t\t{\n\t\t\t\tcase LiteGraph.LEFT: start_offset_x = -1; break;\n\t\t\t\tcase LiteGraph.RIGHT: start_offset_x = 1; break;\n\t\t\t\tcase LiteGraph.UP: start_offset_y = -1; break;\n\t\t\t\tcase LiteGraph.DOWN: start_offset_y = 1; break;\n\t\t\t}\n\t\t\tswitch(end_dir)\n\t\t\t{\n\t\t\t\tcase LiteGraph.LEFT: end_offset_x = -1; break;\n\t\t\t\tcase LiteGraph.RIGHT: end_offset_x = 1; break;\n\t\t\t\tcase LiteGraph.UP: end_offset_y = -1; break;\n\t\t\t\tcase LiteGraph.DOWN: end_offset_y = 1; break;\n\t\t\t}\n\t\t\tvar l = 15;\n\t\t\tctx.lineTo(a[0] + start_offset_x * l, a[1] + start_offset_y * l + offsety);\n\t\t\tctx.lineTo(b[0] + end_offset_x * l, b[1] + end_offset_y * l + offsety);\n\t\t\tctx.lineTo(b[0],b[1] + offsety);\n\t\t}\n\t\telse if(this.links_render_mode == LiteGraph.STRAIGHT_LINK)\n\t\t{\n\t\t\tctx.moveTo(a[0], a[1]);\n\t\t\tvar start_x = a[0];\n\t\t\tvar start_y = a[1];\n\t\t\tvar end_x = b[0];\n\t\t\tvar end_y = b[1];\n\t\t\tif( start_dir == LiteGraph.RIGHT )\n\t\t\t\tstart_x += 10;\n\t\t\telse\n\t\t\t\tstart_y += 10;\n\t\t\tif( end_dir == LiteGraph.LEFT )\n\t\t\t\tend_x -= 10;\n\t\t\telse\n\t\t\t\tend_y -= 10;\n\t\t\tctx.lineTo(start_x, start_y);\n\t\t\tctx.lineTo((start_x + end_x)*0.5,start_y);\n\t\t\tctx.lineTo((start_x + end_x)*0.5,end_y);\n\t\t\tctx.lineTo(end_x, end_y);\n\t\t\tctx.lineTo(b[0],b[1]);\n\t\t}\n\t\telse\n\t\t\treturn; //unknown\n\t}\n\n\t//rendering the outline of the connection can be a little bit slow\n\tif(this.render_connections_border &amp;&amp; this.ds.scale &gt; 0.6 &amp;&amp; !skip_border)\n\t{\n\t\tctx.strokeStyle = &quot;rgba(0,0,0,0.5)&quot;;\n\t\tctx.stroke();\n\t}\n\n\tctx.lineWidth = this.connections_width;\n\tctx.fillStyle = ctx.strokeStyle = color;\n\tctx.stroke();\n\t//end line shape\n\n\tvar pos = this.computeConnectionPoint( a, b, 0.5, start_dir, end_dir );\n\tif(link &amp;&amp; link._pos)\n\t{\n\t\tlink._pos[0] = pos[0];\n\t\tlink._pos[1] = pos[1];\n\t}\n\n\t//render arrow in the middle\n\tif( this.ds.scale &gt;= 0.6 &amp;&amp; this.highquality_render &amp;&amp; end_dir != LiteGraph.CENTER )\n\t{\n\t\t//render arrow\n\t\tif( this.render_connection_arrows )\n\t\t{\n\t\t\t//compute two points in the connection\n\t\t\tvar posA = this.computeConnectionPoint( a, b, 0.25, start_dir, end_dir );\n\t\t\tvar posB = this.computeConnectionPoint( a, b, 0.26, start_dir, end_dir );\n\t\t\tvar posC = this.computeConnectionPoint( a, b, 0.75, start_dir, end_dir );\n\t\t\tvar posD = this.computeConnectionPoint( a, b, 0.76, start_dir, end_dir );\n\n\t\t\t//compute the angle between them so the arrow points in the right direction\n\t\t\tvar angleA = 0;\n\t\t\tvar angleB = 0;\n\t\t\tif(this.render_curved_connections)\n\t\t\t{\n\t\t\t\tangleA = -Math.atan2( posB[0] - posA[0], posB[1] - posA[1]);\n\t\t\t\tangleB = -Math.atan2( posD[0] - posC[0], posD[1] - posC[1]);\n\t\t\t}\n\t\t\telse\n\t\t\t\tangleB = angleA = b[1] &gt; a[1] ? 0 : Math.PI;\n\n\t\t\t//render arrow\n\t\t\tctx.save();\n\t\t\tctx.translate(posA[0],posA[1]);\n\t\t\tctx.rotate(angleA);\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(-5,-3);\n\t\t\tctx.lineTo(0,+7);\n\t\t\tctx.lineTo(+5,-3);\n\t\t\tctx.fill();\n\t\t\tctx.restore();\n\t\t\tctx.save();\n\t\t\tctx.translate(posC[0],posC[1]);\n\t\t\tctx.rotate(angleB);\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(-5,-3);\n\t\t\tctx.lineTo(0,+7);\n\t\t\tctx.lineTo(+5,-3);\n\t\t\tctx.fill();\n\t\t\tctx.restore();\n\t\t}\n\n\t\t//circle\n\t\tctx.beginPath();\n\t\tctx.arc(pos[0],pos[1],5,0,Math.PI*2);\n\t\tctx.fill();\n\t}\n\n\t//render flowing points\n\tif(flow)\n\t{\n\t\tctx.fillStyle = color;\n\t\tfor(var i = 0; i &lt; 5; ++i)\n\t\t{\n\t\t\tvar f = (LiteGraph.getTime() * 0.001 + (i * 0.2)) % 1;\n\t\t\tvar pos = this.computeConnectionPoint(a,b,f, start_dir, end_dir);\n\t\t\tctx.beginPath();\n\t\t\tctx.arc(pos[0],pos[1],5,0,2*Math.PI);\n\t\t\tctx.fill();\n\t\t}\n\t}\n}\n\n//returns the link center point based on curvature\nLGraphCanvas.prototype.computeConnectionPoint = function(a,b,t,start_dir,end_dir)\n{\n\tstart_dir = start_dir || LiteGraph.RIGHT;\n\tend_dir = end_dir || LiteGraph.LEFT;\n\n\tvar dist = distance(a,b);\n\tvar p0 = a;\n\tvar p1 = [ a[0], a[1] ];\n\tvar p2 = [ b[0], b[1] ];\n\tvar p3 = b;\n\n\tswitch(start_dir)\n\t{\n\t\tcase LiteGraph.LEFT: p1[0] += dist*-0.25; break;\n\t\tcase LiteGraph.RIGHT: p1[0] += dist*0.25; break;\n\t\tcase LiteGraph.UP: p1[1] += dist*-0.25; break;\n\t\tcase LiteGraph.DOWN: p1[1] += dist*0.25; break;\n\t}\n\tswitch(end_dir)\n\t{\n\t\tcase LiteGraph.LEFT: p2[0] += dist*-0.25; break;\n\t\tcase LiteGraph.RIGHT: p2[0] += dist*0.25; break;\n\t\tcase LiteGraph.UP: p2[1] += dist*-0.25; break;\n\t\tcase LiteGraph.DOWN: p2[1] += dist*0.25; break;\n\t}\n\n\tvar c1 = (1-t)*(1-t)*(1-t);\n\tvar c2 = 3*((1-t)*(1-t))*t;\n\tvar c3 = 3*(1-t)*(t*t);\n\tvar c4 = t*t*t;\n\n\tvar x = c1*p0[0] + c2*p1[0] + c3*p2[0] + c4*p3[0];\n\tvar y = c1*p0[1] + c2*p1[1] + c3*p2[1] + c4*p3[1];\n\treturn [x,y];\n}\n\nLGraphCanvas.prototype.drawExecutionOrder = function(ctx)\n{\n\tctx.shadowColor = &quot;transparent&quot;;\n\tctx.globalAlpha = 0.25;\n\n\tctx.textAlign = &quot;center&quot;;\n\tctx.strokeStyle = &quot;white&quot;;\n\tctx.globalAlpha = 0.75;\n\n\tvar visible_nodes = this.visible_nodes;\n\tfor (var i = 0; i &lt; visible_nodes.length; ++i)\n\t{\n\t\tvar node = visible_nodes[i];\n\t\tctx.fillStyle = &quot;black&quot;;\n\t\tctx.fillRect( node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT, node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT );\n\t\tif(node.order == 0)\n\t\t\tctx.strokeRect( node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT );\n\t\tctx.fillStyle = &quot;#FFF&quot;;\n\t\tctx.fillText( node.order, node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5, node.pos[1] - 6 );\n\t}\n\tctx.globalAlpha = 1;\n}\n\n\n/**\n* draws the widgets stored inside a node\n* @method drawNodeWidgets\n**/\nLGraphCanvas.prototype.drawNodeWidgets = function( node, posY, ctx, active_widget )\n{\n\tif(!node.widgets || !node.widgets.length)\n\t\treturn 0;\n\tvar width = node.size[0];\n\tvar widgets = node.widgets;\n\tposY += 2;\n\tvar H = LiteGraph.NODE_WIDGET_HEIGHT;\n\tvar show_text = this.ds.scale &gt; 0.5;\n\tctx.save();\n\tctx.globalAlpha = this.editor_alpha;\n\tvar outline_color = &quot;#666&quot;;\n\tvar background_color = &quot;#222&quot;;\n\tvar margin = 15;\n\n\tfor(var i = 0; i &lt; widgets.length; ++i)\n\t{\n\t\tvar w = widgets[i];\n\t\tvar y = posY;\n\t\tif(w.y)\n\t\t\ty = w.y;\n\t\tw.last_y = y;\n\t\tctx.strokeStyle = outline_color;\n\t\tctx.fillStyle = &quot;#222&quot;;\n\t\tctx.textAlign = &quot;left&quot;;\n\n\t\tswitch( w.type )\n\t\t{\n\t\t\tcase &quot;button&quot;: \n\t\t\t\tif(w.clicked)\n\t\t\t\t{\n\t\t\t\t\tctx.fillStyle = &quot;#AAA&quot;;\n\t\t\t\t\tw.clicked = false;\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t}\n\t\t\t\tctx.fillRect(margin,y,width-margin*2,H);\n\t\t\t\tctx.strokeRect(margin,y,width-margin*2,H);\n\t\t\t\tif(show_text)\n\t\t\t\t{\n\t\t\t\t\tctx.textAlign = &quot;center&quot;;\n\t\t\t\t\tctx.fillStyle = &quot;#AAA&quot;;\n\t\t\t\t\tctx.fillText( w.name, width*0.5, y + H*0.7 );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase &quot;toggle&quot;:\n\t\t\t\tctx.textAlign = &quot;left&quot;;\n\t\t\t\tctx.strokeStyle = outline_color;\n\t\t\t\tctx.fillStyle = background_color;\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.roundRect( margin, posY, width - margin*2, H,H*0.5 );\n\t\t\t\tctx.fill();\n\t\t\t\tctx.stroke();\n\t\t\t\tctx.fillStyle = w.value ? &quot;#89A&quot; : &quot;#333&quot;;\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc( width - margin*2, y + H*0.5, H * 0.36, 0, Math.PI * 2 );\n\t\t\t\tctx.fill();\n\t\t\t\tif(show_text)\n\t\t\t\t{\n\t\t\t\t\tctx.fillStyle = &quot;#999&quot;;\n\t\t\t\t\tif(w.name != null)\n\t\t\t\t\t\tctx.fillText( w.name, margin*2, y + H*0.7 );\n\t\t\t\t\tctx.fillStyle = w.value ? &quot;#DDD&quot; : &quot;#888&quot;;\n\t\t\t\t\tctx.textAlign = &quot;right&quot;;\n\t\t\t\t\tctx.fillText( w.value ? (w.options.on || &quot;true&quot;) : (w.options.off || &quot;false&quot;), width - 40, y + H*0.7 );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase &quot;slider&quot;: \n\t\t\t\tctx.fillStyle = background_color;\n\t\t\t\tctx.fillRect(margin,y,width-margin*2,H);\n\t\t\t\tvar range = w.options.max - w.options.min;\n\t\t\t\tvar nvalue = (w.value - w.options.min) / range;\n\t\t\t\tctx.fillStyle = active_widget == w ? &quot;#89A&quot; : &quot;#678&quot;;\n\t\t\t\tctx.fillRect(margin,y,nvalue*(width-margin*2),H);\n\t\t\t\tctx.strokeRect(margin,y,width-margin*2,H);\n\t\t\t\tif( w.marker )\n\t\t\t\t{\n\t\t\t\t\tvar marker_nvalue = (w.marker - w.options.min) / range;\n\t\t\t\t\tctx.fillStyle = &quot;#AA9&quot;;\n\t\t\t\t\tctx.fillRect(margin + marker_nvalue*(width-margin*2),y,2,H);\n\t\t\t\t}\n\t\t\t\tif(show_text)\n\t\t\t\t{\n\t\t\t\t\tctx.textAlign = &quot;center&quot;;\n\t\t\t\t\tctx.fillStyle = &quot;#DDD&quot;;\n\t\t\t\t\tctx.fillText( w.name + &quot;  &quot; + Number(w.value).toFixed(3), width*0.5, y + H*0.7 );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase &quot;number&quot;:\n\t\t\tcase &quot;combo&quot;:\n\t\t\t\tctx.textAlign = &quot;left&quot;;\n\t\t\t\tctx.strokeStyle = outline_color;\n\t\t\t\tctx.fillStyle = background_color;\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.roundRect( margin, posY, width - margin*2, H,H*0.5 );\n\t\t\t\tctx.fill();\n\t\t\t\tctx.stroke();\n\t\t\t\tif(show_text)\n\t\t\t\t{\n\t\t\t\t\tctx.fillStyle = &quot;#AAA&quot;;\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.moveTo( margin + 16, posY + 5 );\n\t\t\t\t\tctx.lineTo( margin + 6, posY + H*0.5 );\n\t\t\t\t\tctx.lineTo( margin + 16, posY + H - 5 );\n\t\t\t\t\tctx.moveTo( width - margin - 16, posY + 5 );\n\t\t\t\t\tctx.lineTo( width - margin - 6, posY + H*0.5 );\n\t\t\t\t\tctx.lineTo( width - margin - 16, posY + H - 5 );\n\t\t\t\t\tctx.fill();\n\t\t\t\t\tctx.fillStyle = &quot;#999&quot;;\n\t\t\t\t\tctx.fillText( w.name,  margin*2 + 5, y + H*0.7 );\n\t\t\t\t\tctx.fillStyle = &quot;#DDD&quot;;\n\t\t\t\t\tctx.textAlign = &quot;right&quot;;\n\t\t\t\t\tif(w.type == &quot;number&quot;)\n\t\t\t\t\t\tctx.fillText( Number(w.value).toFixed( w.options.precision !== undefined ? w.options.precision : 3), width - margin*2 - 20, y + H*0.7 );\n\t\t\t\t\telse\n\t\t\t\t\t\tctx.fillText( w.value, width - margin*2 - 20, y + H*0.7 );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase &quot;string&quot;:\n\t\t\tcase &quot;text&quot;:\n\t\t\t\tctx.textAlign = &quot;left&quot;;\n\t\t\t\tctx.strokeStyle = outline_color;\n\t\t\t\tctx.fillStyle = background_color;\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.roundRect( margin, posY, width - margin*2, H,H*0.5 );\n\t\t\t\tctx.fill();\n\t\t\t\tctx.stroke();\n\t\t\t\tif(show_text)\n\t\t\t\t{\n\t\t\t\t\tctx.fillStyle = &quot;#999&quot;;\n\t\t\t\t\tif(w.name != null)\n\t\t\t\t\t\tctx.fillText( w.name, margin*2, y + H*0.7 );\n\t\t\t\t\tctx.fillStyle = &quot;#DDD&quot;;\n\t\t\t\t\tctx.textAlign = &quot;right&quot;;\n\t\t\t\t\tctx.fillText( w.value, width - margin*2, y + H*0.7 );\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tif(w.draw)\n\t\t\t\t\tw.draw(ctx,node,w,y,H);\n\t\t\t\tbreak;\n\t\t}\n\t\tposY += H + 4;\n\t}\n\tctx.restore();\n}\n\n/**\n* process an event on widgets \n* @method processNodeWidgets\n**/\nLGraphCanvas.prototype.processNodeWidgets = function( node, pos, event, active_widget )\n{\n\tif(!node.widgets || !node.widgets.length)\n\t\treturn null;\n\n\tvar x = pos[0] - node.pos[0];\n\tvar y = pos[1] - node.pos[1];\n\tvar width = node.size[0];\n\tvar that = this;\n\tvar ref_window = this.getCanvasWindow();\n\n\tfor(var i = 0; i &lt; node.widgets.length; ++i)\n\t{\n\t\tvar w = node.widgets[i];\n\t\tif( w == active_widget || (x &gt; 6 &amp;&amp; x &lt; (width - 12) &amp;&amp; y &gt; w.last_y &amp;&amp; y &lt; (w.last_y + LiteGraph.NODE_WIDGET_HEIGHT)) )\n\t\t{\n\t\t\t//inside widget\n\t\t\tswitch( w.type )\n\t\t\t{\n\t\t\t\tcase &quot;button&quot;: \n\t\t\t\t\tif(w.callback)\n\t\t\t\t\t\tsetTimeout( function(){\tw.callback( w, that, node, pos ); }, 20 );\n\t\t\t\t\tw.clicked = true;\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase &quot;slider&quot;: \n\t\t\t\t\tvar range = w.options.max - w.options.min;\n\t\t\t\t\tvar nvalue = Math.clamp( (x - 10) / (width - 20), 0, 1);\n\t\t\t\t\tw.value = w.options.min + (w.options.max - w.options.min) * nvalue;\n\t\t\t\t\tif(w.callback)\n\t\t\t\t\t\tsetTimeout( function(){\tw.callback( w.value, that, node, pos ); }, 20 );\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase &quot;number&quot;: \n\t\t\t\tcase &quot;combo&quot;: \n\t\t\t\t\tif(event.type == &quot;mousemove&quot; &amp;&amp; w.type == &quot;number&quot;)\n\t\t\t\t\t{\n\t\t\t\t\t\tw.value += (event.deltaX * 0.1) * (w.options.step || 1);\n\t\t\t\t\t\tif(w.options.min != null &amp;&amp; w.value &lt; w.options.min)\n\t\t\t\t\t\t\tw.value = w.options.min;\n\t\t\t\t\t\tif(w.options.max != null &amp;&amp; w.value &gt; w.options.max)\n\t\t\t\t\t\t\tw.value = w.options.max;\n\t\t\t\t\t}\n\t\t\t\t\telse if( event.type == &quot;mousedown&quot; )\n\t\t\t\t\t{\n\t\t\t\t\t\tvar values = w.options.values;\n\t\t\t\t\t\tif(values &amp;&amp; values.constructor === Function)\n\t\t\t\t\t\t\tvalues = w.options.values( w, node );\n\n\t\t\t\t\t\tvar delta = ( x &lt; 40 ? -1 : ( x &gt; width - 40 ? 1 : 0) );\n\t\t\t\t\t\tif (w.type == &quot;number&quot;)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tw.value += delta * 0.1 * (w.options.step || 1);\n\t\t\t\t\t\t\tif(w.options.min != null &amp;&amp; w.value &lt; w.options.min)\n\t\t\t\t\t\t\t\tw.value = w.options.min;\n\t\t\t\t\t\t\tif(w.options.max != null &amp;&amp; w.value &gt; w.options.max)\n\t\t\t\t\t\t\t\tw.value = w.options.max;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if(delta)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar index = values.indexOf( w.value ) + delta;\n\t\t\t\t\t\t\tif( index &gt;= values.length )\n\t\t\t\t\t\t\t\tindex = 0;\n\t\t\t\t\t\t\tif( index &lt; 0 )\n\t\t\t\t\t\t\t\tindex = values.length - 1;\n\t\t\t\t\t\t\tw.value = values[ index ];\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar menu = new LiteGraph.ContextMenu( values, { scale: Math.max(1,this.ds.scale), event: event, className: &quot;dark&quot;, callback: inner_clicked.bind(w) }, ref_window );\n\t\t\t\t\t\t\tfunction inner_clicked( v, option, event )\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis.value = v;\n\t\t\t\t\t\t\t\tthat.dirty_canvas = true;\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif(w.callback)\n\t\t\t\t\t\tsetTimeout( (function(){ this.callback( this.value, that, node, pos ); }).bind(w), 20 );\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase &quot;toggle&quot;:\n\t\t\t\t\tif( event.type == &quot;mousedown&quot; )\n\t\t\t\t\t{\n\t\t\t\t\t\tw.value = !w.value;\n\t\t\t\t\t\tif(w.callback)\n\t\t\t\t\t\t\tsetTimeout( function(){\tw.callback( w.value, that, node, pos ); }, 20 );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase &quot;string&quot;:\n\t\t\t\tcase &quot;text&quot;:\n\t\t\t\t\tif( event.type == &quot;mousedown&quot; )\n\t\t\t\t\t\tthis.prompt( &quot;Value&quot;, w.value, (function(v){ this.value = v; if(w.callback) w.callback(v, that, node ); }).bind(w), event );\n\t\t\t\t\tbreak;\n\t\t\t\tdefault: \n\t\t\t\t\tif( w.mouse )\n\t\t\t\t\t\tw.mouse( ctx, event, [x,y], node );\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\treturn w;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n* draws every group area in the background\n* @method drawGroups\n**/\nLGraphCanvas.prototype.drawGroups = function(canvas, ctx)\n{\n\tif(!this.graph)\n\t\treturn;\n\n\tvar groups = this.graph._groups;\n\n\tctx.save();\n\tctx.globalAlpha = 0.5 * this.editor_alpha;\n\n\tfor(var i = 0; i &lt; groups.length; ++i)\n\t{\n\t\tvar group = groups[i];\n\n\t\tif(!overlapBounding( this.visible_area, group._bounding ))\n\t\t\tcontinue; //out of the visible area\n\n\t\tctx.fillStyle = group.color || &quot;#335&quot;;\n\t\tctx.strokeStyle = group.color || &quot;#335&quot;;\n\t\tvar pos = group._pos;\n\t\tvar size = group._size;\n\t\tctx.globalAlpha = 0.25 * this.editor_alpha;\n\t\tctx.beginPath();\n\t\tctx.rect( pos[0] + 0.5, pos[1] + 0.5, size[0], size[1] );\n\t\tctx.fill();\n\t\tctx.globalAlpha = this.editor_alpha;;\n\t\tctx.stroke();\n\n\t\tctx.beginPath();\n\t\tctx.moveTo( pos[0] + size[0], pos[1] + size[1] );\n\t\tctx.lineTo( pos[0] + size[0] - 10, pos[1] + size[1] );\n\t\tctx.lineTo( pos[0] + size[0], pos[1] + size[1] - 10 );\n\t\tctx.fill();\n\n\t\tvar font_size = (group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE);\n\t\tctx.font = font_size + &quot;px Arial&quot;;\n\t\tctx.fillText( group.title, pos[0] + 4, pos[1] + font_size );\n\t}\n\n\tctx.restore();\n}\n\nLGraphCanvas.prototype.adjustNodesSize = function()\n{\n\tvar nodes = this.graph._nodes;\n\tfor(var i = 0; i &lt; nodes.length; ++i)\n\t\tnodes[i].size = nodes[i].computeSize();\n\tthis.setDirty(true,true);\n}\n\n\n/**\n* resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode\n* @method resize\n**/\nLGraphCanvas.prototype.resize = function(width, height)\n{\n\tif(!width &amp;&amp; !height)\n\t{\n\t\tvar parent = this.canvas.parentNode;\n\t\twidth = parent.offsetWidth;\n\t\theight = parent.offsetHeight;\n\t}\n\n\tif(this.canvas.width == width &amp;&amp; this.canvas.height == height)\n\t\treturn;\n\n\tthis.canvas.width = width;\n\tthis.canvas.height = height;\n\tthis.bgcanvas.width = this.canvas.width;\n\tthis.bgcanvas.height = this.canvas.height;\n\tthis.setDirty(true,true);\n}\n\n/**\n* switches to live mode (node shapes are not rendered, only the content)\n* this feature was designed when graphs where meant to create user interfaces\n* @method switchLiveMode\n**/\nLGraphCanvas.prototype.switchLiveMode = function(transition)\n{\n\tif(!transition)\n\t{\n\t\tthis.live_mode = !this.live_mode;\n\t\tthis.dirty_canvas = true;\n\t\tthis.dirty_bgcanvas = true;\n\t\treturn;\n\t}\n\n\tvar self = this;\n\tvar delta = this.live_mode ? 1.1 : 0.9;\n\tif(this.live_mode)\n\t{\n\t\tthis.live_mode = false;\n\t\tthis.editor_alpha = 0.1;\n\t}\n\n\tvar t = setInterval(function() {\n\t\tself.editor_alpha *= delta;\n\t\tself.dirty_canvas = true;\n\t\tself.dirty_bgcanvas = true;\n\n\t\tif(delta &lt; 1  &amp;&amp; self.editor_alpha &lt; 0.01)\n\t\t{\n\t\t\tclearInterval(t);\n\t\t\tif(delta &lt; 1)\n\t\t\t\tself.live_mode = true;\n\t\t}\n\t\tif(delta &gt; 1 &amp;&amp; self.editor_alpha &gt; 0.99)\n\t\t{\n\t\t\tclearInterval(t);\n\t\t\tself.editor_alpha = 1;\n\t\t}\n\t},1);\n}\n\nLGraphCanvas.prototype.onNodeSelectionChange = function(node)\n{\n\treturn; //disabled\n}\n\nLGraphCanvas.prototype.touchHandler = function(event)\n{\n\t//alert(&quot;foo&quot;);\n    var touches = event.changedTouches,\n        first = touches[0],\n        type = &quot;&quot;;\n\n         switch(event.type)\n    {\n        case &quot;touchstart&quot;: type = &quot;mousedown&quot;; break;\n        case &quot;touchmove&quot;:  type = &quot;mousemove&quot;; break;\n        case &quot;touchend&quot;:   type = &quot;mouseup&quot;; break;\n        default: return;\n    }\n\n             //initMouseEvent(type, canBubble, cancelable, view, clickCount,\n    //           screenX, screenY, clientX, clientY, ctrlKey,\n    //           altKey, shiftKey, metaKey, button, relatedTarget);\n\n\tvar window = this.getCanvasWindow();\n\tvar document = window.document;\n\n    var simulatedEvent = document.createEvent(&quot;MouseEvent&quot;);\n    simulatedEvent.initMouseEvent(type, true, true, window, 1,\n                              first.screenX, first.screenY,\n                              first.clientX, first.clientY, false,\n                              false, false, false, 0/*left*/, null);\n\tfirst.target.dispatchEvent(simulatedEvent);\n    event.preventDefault();\n}\n\n/* CONTEXT MENU ********************/\n\nLGraphCanvas.onGroupAdd = function(info,entry,mouse_event)\n{\n\tvar canvas = LGraphCanvas.active_canvas;\n\tvar ref_window = canvas.getCanvasWindow();\n\t\t\n\tvar group = new LiteGraph.LGraphGroup();\n\tgroup.pos = canvas.convertEventToCanvasOffset( mouse_event );\n\tcanvas.graph.add( group );\n}\n\nLGraphCanvas.onMenuAdd = function( node, options, e, prev_menu )\n{\n\tvar canvas = LGraphCanvas.active_canvas;\n\tvar ref_window = canvas.getCanvasWindow();\n\n\tvar values = LiteGraph.getNodeTypesCategories();\n\tvar entries = [];\n\tfor(var i in values)\n\t\tif(values[i])\n\t\t\tentries.push({ value: values[i], content: values[i], has_submenu: true });\n\n\t//show categories\n\tvar menu = new LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu }, ref_window);\n\n\tfunction inner_clicked( v, option, e )\n\t{\n\t\tvar category = v.value;\n\t\tvar node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter );\n\t\tvar values = [];\n\t\tfor(var i in node_types)\n\t\t\tif (!node_types[i].skip_list)\n\t\t\t\tvalues.push( { content: node_types[i].title, value: node_types[i].type });\n\n\t\tnew LiteGraph.ContextMenu( values, {event: e, callback: inner_create, parentMenu: menu }, ref_window);\n\t\treturn false;\n\t}\n\n\tfunction inner_create( v, e )\n\t{\n\t\tvar first_event = prev_menu.getFirstEvent();\n\t\tvar node = LiteGraph.createNode( v.value );\n\t\tif(node)\n\t\t{\n\t\t\tnode.pos = canvas.convertEventToCanvasOffset( first_event );\n\t\t\tcanvas.graph.add( node );\n\t\t}\n\t}\n\n\treturn false;\n}\n\nLGraphCanvas.onMenuCollapseAll = function()\n{\n\n}\n\n\nLGraphCanvas.onMenuNodeEdit = function()\n{\n\n}\n\nLGraphCanvas.showMenuNodeOptionalInputs = function( v, options, e, prev_menu, node )\n{\n\tif(!node)\n\t\treturn;\n\n\tvar that = this;\n\tvar canvas = LGraphCanvas.active_canvas;\n\tvar ref_window = canvas.getCanvasWindow();\n\n\tvar options = node.optional_inputs;\n\tif(node.onGetInputs)\n\t\toptions = node.onGetInputs();\n\n\tvar entries = [];\n\tif(options)\n\t\tfor (var i in options)\n\t\t{\n\t\t\tvar entry = options[i];\n\t\t\tif(!entry)\n\t\t\t{\n\t\t\t\tentries.push(null);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvar label = entry[0];\n\t\t\tif(entry[2] &amp;&amp; entry[2].label)\n\t\t\t\tlabel = entry[2].label;\n\t\t\tvar data = {content: label, value: entry};\n\t\t\tif(entry[1] == LiteGraph.ACTION)\n\t\t\t\tdata.className = &quot;event&quot;;\n\t\t\tentries.push(data);\n\t\t}\n\n\tif(this.onMenuNodeInputs)\n\t\tentries = this.onMenuNodeInputs( entries );\n\n\tif(!entries.length)\n\t\treturn;\n\n\tvar menu = new LiteGraph.ContextMenu(entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window);\n\n\tfunction inner_clicked(v, e, prev)\n\t{\n\t\tif(!node)\n\t\t\treturn;\n\n\t\tif(v.callback)\n\t\t\tv.callback.call( that, node, v, e, prev );\n\n\t\tif(v.value)\n\t\t{\n\t\t\tnode.addInput(v.value[0],v.value[1], v.value[2]);\n\t\t\tnode.setDirtyCanvas(true,true);\n\t\t}\n\t}\n\n\treturn false;\n}\n\nLGraphCanvas.showMenuNodeOptionalOutputs = function( v, options, e, prev_menu, node )\n{\n\tif(!node)\n\t\treturn;\n\n\tvar that = this;\n\tvar canvas = LGraphCanvas.active_canvas;\n\tvar ref_window = canvas.getCanvasWindow();\n\n\tvar options = node.optional_outputs;\n\tif(node.onGetOutputs)\n\t\toptions = node.onGetOutputs();\n\n\tvar entries = [];\n\tif(options)\n\t\tfor (var i in options)\n\t\t{\n\t\t\tvar entry = options[i];\n\t\t\tif(!entry) //separator?\n\t\t\t{\n\t\t\t\tentries.push(null);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif(node.flags &amp;&amp; node.flags.skip_repeated_outputs &amp;&amp; node.findOutputSlot(entry[0]) != -1)\n\t\t\t\tcontinue; //skip the ones already on\n\t\t\tvar label = entry[0];\n\t\t\tif(entry[2] &amp;&amp; entry[2].label)\n\t\t\t\tlabel = entry[2].label;\n\t\t\tvar data = {content: label, value: entry};\n\t\t\tif(entry[1] == LiteGraph.EVENT)\n\t\t\t\tdata.className = &quot;event&quot;;\n\t\t\tentries.push(data);\n\t\t}\n\n\tif(this.onMenuNodeOutputs)\n\t\tentries = this.onMenuNodeOutputs( entries );\n\n\tif(!entries.length)\n\t\treturn;\n\n\tvar menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, node: node }, ref_window);\n\n\tfunction inner_clicked( v, e, prev )\n\t{\n\t\tif(!node)\n\t\t\treturn;\n\n\t\tif(v.callback)\n\t\t\tv.callback.call( that, node, v, e, prev );\n\n\t\tif(!v.value)\n\t\t\treturn;\n\n\t\tvar value = v.value[1];\n\n\t\tif(value &amp;&amp; (value.constructor === Object || value.constructor === Array)) //submenu why?\n\t\t{\n\t\t\tvar entries = [];\n\t\t\tfor(var i in value)\n\t\t\t\tentries.push({ content: i, value: value[i]});\n\t\t\tnew LiteGraph.ContextMenu( entries, { event: e, callback: inner_clicked, parentMenu: prev_menu, node: node });\n\t\t\treturn false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnode.addOutput( v.value[0], v.value[1], v.value[2]);\n\t\t\tnode.setDirtyCanvas(true,true);\n\t\t}\n\n\t}\n\n\treturn false;\n}\n\nLGraphCanvas.onShowMenuNodeProperties = function( value, options, e, prev_menu, node )\n{\n\tif(!node || !node.properties)\n\t\treturn;\n\n\tvar that = this;\n\tvar canvas = LGraphCanvas.active_canvas;\n\tvar ref_window = canvas.getCanvasWindow();\n\n\tvar entries = [];\n\t\tfor (var i in node.properties)\n\t\t{\n\t\t\tvar value = node.properties[i] !== undefined ? node.properties[i] : &quot; &quot;;\n\t\t\t//value could contain invalid html characters, clean that\n\t\t\tvalue = LGraphCanvas.decodeHTML(value);\n\t\t\tentries.push({content: &quot;&lt;span class=&#x27;property_name&#x27;&gt;&quot; + i + &quot;&lt;/span&gt;&quot; + &quot;&lt;span class=&#x27;property_value&#x27;&gt;&quot; + value + &quot;&lt;/span&gt;&quot;, value: i});\n\t\t}\n\tif(!entries.length)\n\t\treturn;\n\n\tvar menu = new LiteGraph.ContextMenu(entries, {event: e, callback: inner_clicked, parentMenu: prev_menu, allow_html: true, node: node },ref_window);\n\n\tfunction inner_clicked( v, options, e, prev )\n\t{\n\t\tif(!node)\n\t\t\treturn;\n\t\tvar rect = this.getBoundingClientRect();\n\t\tcanvas.showEditPropertyValue( node, v.value, { position: [rect.left, rect.top] });\n\t}\n\n\treturn false;\n}\n\nLGraphCanvas.decodeHTML = function( str )\n{\n\tvar e = document.createElement(&quot;div&quot;);\n\te.innerText = str;\n\treturn e.innerHTML;\n}\n\nLGraphCanvas.onResizeNode = function( value, options, e, menu, node )\n{\n\tif(!node)\n\t\treturn;\n\tnode.size = node.computeSize();\n\tnode.setDirtyCanvas(true,true);\n}\n\nLGraphCanvas.prototype.showLinkMenu = function( link, e )\n{\n\tvar that = this;\n\n\tnew LiteGraph.ContextMenu([&quot;Delete&quot;], { event: e, callback: inner_clicked });\n\n\tfunction inner_clicked(v)\n\t{\n\t\tswitch(v)\n\t\t{\n\t\t\tcase &quot;Delete&quot;: that.graph.removeLink( link.id ); break;\n\t\t\tdefault:\n\t\t}\n\t}\n\n\treturn false;\n}\n\nLGraphCanvas.onShowPropertyEditor = function( item, options, e, menu, node )\n{\n\tvar input_html = &quot;&quot;;\n\tvar property = item.property || &quot;title&quot;;\n\tvar value = node[ property ];\n\n\tvar dialog = document.createElement(&quot;div&quot;);\n\tdialog.className = &quot;graphdialog&quot;;\n\tdialog.innerHTML = &quot;&lt;span class=&#x27;name&#x27;&gt;&lt;/span&gt;&lt;input autofocus type=&#x27;text&#x27; class=&#x27;value&#x27;/&gt;&lt;button&gt;OK&lt;/button&gt;&quot;;\n\tvar title = dialog.querySelector(&quot;.name&quot;);\n\ttitle.innerText = property;\n\tvar input = dialog.querySelector(&quot;input&quot;);\n\tif(input)\n\t{\n\t\tinput.value = value;\n        input.addEventListener(&quot;blur&quot;, function(e){\n            this.focus();\n        });\n\t\tinput.addEventListener(&quot;keydown&quot;, function(e){\n\t\t\tif(e.keyCode != 13)\n\t\t\t\treturn;\n\t\t\tinner();\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t});\n\t}\n\n\tvar graphcanvas = LGraphCanvas.active_canvas;\n\tvar canvas = graphcanvas.canvas;\n\n\tvar rect = canvas.getBoundingClientRect();\n\tvar offsetx = -20;\n\tvar offsety = -20;\n\tif(rect)\n\t{\n\t\toffsetx -= rect.left;\n\t\toffsety -= rect.top;\n\t}\n\n\tif( event )\n\t{\n\t\tdialog.style.left = (event.clientX + offsetx) + &quot;px&quot;;\n\t\tdialog.style.top = (event.clientY + offsety)+ &quot;px&quot;;\n\t}\n\telse\n\t{\n\t\tdialog.style.left = (canvas.width * 0.5 + offsetx) + &quot;px&quot;;\n\t\tdialog.style.top = (canvas.height * 0.5 + offsety) + &quot;px&quot;;\n\t}\n\n\tvar button = dialog.querySelector(&quot;button&quot;);\n\tbutton.addEventListener(&quot;click&quot;, inner );\n\tcanvas.parentNode.appendChild( dialog );\n\n\tfunction inner()\n\t{\n\t\tsetValue( input.value );\n\t}\n\n\tfunction setValue(value)\n\t{\n\t\tif( item.type == &quot;Number&quot; )\n\t\t\tvalue = Number(value);\n\t\telse if( item.type == &quot;Boolean&quot; )\n\t\t\tvalue = Boolean(value);\n\t\tnode[ property ] = value;\n\t\tif(dialog.parentNode)\n\t\t\tdialog.parentNode.removeChild( dialog );\n\t\tnode.setDirtyCanvas(true,true);\n\t}\n}\n\nLGraphCanvas.prototype.prompt = function( title, value, callback, event )\n{\n\tvar that = this;\n\tvar input_html = &quot;&quot;;\n\ttitle = title || &quot;&quot;;\n\n\tvar modified = false;\n\n\tvar dialog = document.createElement(&quot;div&quot;);\n\tdialog.className = &quot;graphdialog rounded&quot;;\n\tdialog.innerHTML = &quot;&lt;span class=&#x27;name&#x27;&gt;&lt;/span&gt; &lt;input autofocus type=&#x27;text&#x27; class=&#x27;value&#x27;/&gt;&lt;button class=&#x27;rounded&#x27;&gt;OK&lt;/button&gt;&quot;;\n\tdialog.close = function()\n\t{\n\t\tthat.prompt_box = null;\n\t\tif(dialog.parentNode)\n\t\t\tdialog.parentNode.removeChild( dialog );\n\t}\n\n\tif(this.ds.scale &gt; 1)\n\t\tdialog.style.transform = &quot;scale(&quot;+this.ds.scale+&quot;)&quot;;\n\n\tdialog.addEventListener(&quot;mouseleave&quot;,function(e){\n\t\tif(!modified)\n\t\t\t dialog.close();\n\t});\n\n\tif(that.prompt_box)\n\t\tthat.prompt_box.close();\n\tthat.prompt_box = dialog;\n\n\tvar first = null;\n\tvar timeout = null;\n\tvar selected = null;\n\n\tvar name_element = dialog.querySelector(&quot;.name&quot;);\n\tname_element.innerText = title;\n\tvar value_element = dialog.querySelector(&quot;.value&quot;);\n\tvalue_element.value = value;\n\n\tvar input = dialog.querySelector(&quot;input&quot;);\n\tinput.addEventListener(&quot;keydown&quot;, function(e){\n\t\tmodified = true;\n\t\tif(e.keyCode == 27) //ESC\n\t\t\tdialog.close();\n\t\telse if(e.keyCode == 13)\n\t\t{\n\t\t\tif( callback )\n\t\t\t\tcallback( this.value );\n\t\t\tdialog.close();\n\t\t}\n\t\telse\n\t\t\treturn;\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t});\n\n\tvar button = dialog.querySelector(&quot;button&quot;);\n\tbutton.addEventListener(&quot;click&quot;, function(e){\n\t\tif( callback )\n\t\t\tcallback( input.value );\n\t\tthat.setDirty(true);\n\t\tdialog.close();\t\t\n\t});\n\n\tvar graphcanvas = LGraphCanvas.active_canvas;\n\tvar canvas = graphcanvas.canvas;\n\n\tvar rect = canvas.getBoundingClientRect();\n\tvar offsetx = -20;\n\tvar offsety = -20;\n\tif(rect)\n\t{\n\t\toffsetx -= rect.left;\n\t\toffsety -= rect.top;\n\t}\n\n\tif( event )\n\t{\n\t\tdialog.style.left = (event.clientX + offsetx) + &quot;px&quot;;\n\t\tdialog.style.top = (event.clientY + offsety)+ &quot;px&quot;;\n\t}\n\telse\n\t{\n\t\tdialog.style.left = (canvas.width * 0.5 + offsetx) + &quot;px&quot;;\n\t\tdialog.style.top = (canvas.height * 0.5 + offsety) + &quot;px&quot;;\n\t}\n\n\tcanvas.parentNode.appendChild( dialog );\n\tsetTimeout( function(){\tinput.focus(); },10 );\n\n\treturn dialog;\n}\n\n\nLGraphCanvas.search_limit = -1;\nLGraphCanvas.prototype.showSearchBox = function(event)\n{\n\tvar that = this;\n\tvar input_html = &quot;&quot;;\n\n\tvar dialog = document.createElement(&quot;div&quot;);\n\tdialog.className = &quot;litegraph litesearchbox graphdialog rounded&quot;;\n\tdialog.innerHTML = &quot;&lt;span class=&#x27;name&#x27;&gt;Search&lt;/span&gt; &lt;input autofocus type=&#x27;text&#x27; class=&#x27;value rounded&#x27;/&gt;&lt;div class=&#x27;helper&#x27;&gt;&lt;/div&gt;&quot;;\n\tdialog.close = function()\n\t{\n\t\tthat.search_box = null;\n\t\tdocument.body.focus();\n\t\tsetTimeout( function(){ that.canvas.focus(); },20 ); //important, if canvas loses focus keys wont be captured\n\t\tif(dialog.parentNode)\n\t\t\tdialog.parentNode.removeChild( dialog );\n\t}\n\n\tvar timeout_close = null;\n\n\tif(this.ds.scale &gt; 1)\n\t\tdialog.style.transform = &quot;scale(&quot;+this.ds.scale+&quot;)&quot;;\n\n\tdialog.addEventListener(&quot;mouseenter&quot;,function(e){\n\t\tif(timeout_close)\n\t\t{\n\t\t\tclearTimeout(timeout_close);\n\t\t\ttimeout_close = null;\n\t\t}\n\t});\n\n\tdialog.addEventListener(&quot;mouseleave&quot;,function(e){\n\t\t //dialog.close();\n\t\ttimeout_close = setTimeout(function(){\n\t\t\tdialog.close();\n\t\t},500);\n\t});\n\n\tif(that.search_box)\n\t\tthat.search_box.close();\n\tthat.search_box = dialog;\n\n\tvar helper = dialog.querySelector(&quot;.helper&quot;);\n\n\tvar first = null;\n\tvar timeout = null;\n\tvar selected = null;\n\n\tvar input = dialog.querySelector(&quot;input&quot;);\n\tif(input)\n\t{\n        input.addEventListener(&quot;blur&quot;, function(e){\n            this.focus();\n        });\n\t\tinput.addEventListener(&quot;keydown&quot;, function(e){\n\n\t\t\tif(e.keyCode == 38) //UP\n\t\t\t\tchangeSelection(false);\n\t\t\telse if(e.keyCode == 40) //DOWN\n\t\t\t\tchangeSelection(true);\n\t\t\telse if(e.keyCode == 27) //ESC\n\t\t\t\tdialog.close();\n\t\t\telse if(e.keyCode == 13)\n\t\t\t{\n\t\t\t\tif(selected)\n\t\t\t\t\tselect( selected.innerHTML )\n\t\t\t\telse if(first)\n\t\t\t\t\tselect( first );\n\t\t\t\telse\n\t\t\t\t\tdialog.close();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif(timeout)\n\t\t\t\t\tclearInterval(timeout);\n\t\t\t\ttimeout = setTimeout( refreshHelper, 10 );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t});\n\t}\n\n\tvar graphcanvas = LGraphCanvas.active_canvas;\n\tvar canvas = graphcanvas.canvas;\n\n\tvar rect = canvas.getBoundingClientRect();\n\tvar offsetx = -20;\n\tvar offsety = -20;\n\tif(rect)\n\t{\n\t\toffsetx -= rect.left;\n\t\toffsety -= rect.top;\n\t}\n\n\tif( event )\n\t{\n\t\tdialog.style.left = (event.clientX + offsetx) + &quot;px&quot;;\n\t\tdialog.style.top = (event.clientY + offsety)+ &quot;px&quot;;\n\t}\n\telse\n\t{\n\t\tdialog.style.left = (canvas.width * 0.5 + offsetx) + &quot;px&quot;;\n\t\tdialog.style.top = (canvas.height * 0.5 + offsety) + &quot;px&quot;;\n\t}\n\n\tcanvas.parentNode.appendChild( dialog );\n\tinput.focus();\n\n\tfunction select( name )\n\t{\n\t\tif(name)\n\t\t{\n\t\t\tif( that.onSearchBoxSelection )\n\t\t\t\tthat.onSearchBoxSelection( name, event, graphcanvas );\n\t\t\telse\n\t\t\t{\n\t\t\t\tvar extra = LiteGraph.searchbox_extras[ name ];\n\t\t\t\tif( extra )\n\t\t\t\t\tname = extra.type;\n\n\t\t\t\tvar node = LiteGraph.createNode( name );\n\t\t\t\tif(node)\n\t\t\t\t{\n\t\t\t\t\tnode.pos = graphcanvas.convertEventToCanvasOffset( event );\n\t\t\t\t\tgraphcanvas.graph.add( node );\n\t\t\t\t}\n\n\t\t\t\tif( extra &amp;&amp; extra.data )\n\t\t\t\t{\n\t\t\t\t\tif(extra.data.properties)\n\t\t\t\t\t\tfor(var i in extra.data.properties)\n\t\t\t\t\t\t\tnode.addProperty( extra.data.properties[i][0], extra.data.properties[i][0] );\n\t\t\t\t\tif(extra.data.inputs)\n\t\t\t\t\t{\n\t\t\t\t\t\tnode.inputs = [];\n\t\t\t\t\t\tfor(var i in extra.data.inputs)\n\t\t\t\t\t\t\tnode.addOutput( extra.data.inputs[i][0],extra.data.inputs[i][1] );\n\t\t\t\t\t}\n\t\t\t\t\tif(extra.data.outputs)\n\t\t\t\t\t{\n\t\t\t\t\t\tnode.outputs = [];\n\t\t\t\t\t\tfor(var i in extra.data.outputs)\n\t\t\t\t\t\t\tnode.addOutput( extra.data.outputs[i][0],extra.data.outputs[i][1] );\n\t\t\t\t\t}\n\t\t\t\t\tif(extra.data.title)\n\t\t\t\t\t\tnode.title = extra.data.title;\n\t\t\t\t\tif(extra.data.json)\n\t\t\t\t\t\tnode.configure( extra.data.json );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdialog.close();\n\t}\n\n\tfunction changeSelection( forward )\n\t{\n\t\tvar prev = selected;\n\t\tif(selected)\n\t\t\tselected.classList.remove(&quot;selected&quot;);\n\t\tif(!selected)\n\t\t\tselected = forward ? helper.childNodes[0] : helper.childNodes[ helper.childNodes.length ];\n\t\telse\n\t\t{\n\t\t\tselected = forward ? selected.nextSibling : selected.previousSibling;\n\t\t\tif(!selected)\n\t\t\t\tselected = prev;\n\t\t}\n\t\tif(!selected)\n\t\t\treturn;\n\t\tselected.classList.add(&quot;selected&quot;);\n\t\tselected.scrollIntoView();\n\t}\n\n\tfunction refreshHelper() {\n        timeout = null;\n        var str = input.value;\n        first = null;\n        helper.innerHTML = &quot;&quot;;\n        if (!str)\n            return;\n\n        if (that.onSearchBox) {\n            var list = that.onSearchBox( help, str, graphcanvas );\n\t\t\tif(list)\n\t\t\t\tfor( var i = 0; i &lt; list.length; ++i )\n\t\t\t\t\taddResult( list[i] );\n    \t} else {\n            var c = 0;\n       \t\tstr = str.toLowerCase();\n\t\t\t//extras\n\t\t\tfor(var i in LiteGraph.searchbox_extras)\n\t\t\t{\n\t\t\t\tvar extra = LiteGraph.searchbox_extras[i];\n\t\t\t\tif( extra.desc.toLowerCase().indexOf(str) === -1 )\n\t\t\t\t\tcontinue;\n\t\t\t\taddResult( extra.desc, &quot;searchbox_extra&quot; );\n\t\t\t\tif(LGraphCanvas.search_limit !== -1 &amp;&amp; c++ &gt; LGraphCanvas.search_limit )\n\t\t\t\t\tbreak;\n\t\t\t}\n\n        \tif(Array.prototype.filter)//filter supported\n\t\t\t{\n\t\t\t\t//types\n        \t\tvar keys = Object.keys( LiteGraph.registered_node_types );\n        \t\tvar filtered = keys.filter(function (item) {\n\t\t\t\t\treturn item.toLowerCase().indexOf(str) !== -1;\n                });\n        \t\tfor(var i = 0; i &lt; filtered.length; i++)\n\t\t\t\t{\n                    addResult(filtered[i]);\n                    if(LGraphCanvas.search_limit !== -1 &amp;&amp; c++ &gt; LGraphCanvas.search_limit)\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n                for (var i in LiteGraph.registered_node_types)\n\t\t\t\t{\n                    if (i.indexOf(str) != -1) {\n                        addResult(i);\n                        if(LGraphCanvas.search_limit !== -1 &amp;&amp; c++ &gt; LGraphCanvas.search_limit)\n\t\t\t\t\t\t\tbreak;\n                    }\n                }\n            }\n        }\n\n\t\tfunction addResult( type, className )\n\t\t{\n\t\t\tvar help = document.createElement(&quot;div&quot;);\n\t\t\tif (!first)\n\t\t\t\tfirst = type;\n\t\t\thelp.innerText = type;\n\t\t\thelp.dataset[&quot;type&quot;] = escape(type);\n\t\t\thelp.className = &quot;litegraph lite-search-item&quot;;\n\t\t\tif( className )\n\t\t\t\thelp.className +=  &quot; &quot; + className;\n\t\t\thelp.addEventListener(&quot;click&quot;, function (e) {\n\t\t\t\tselect( unescape( this.dataset[&quot;type&quot;] ) );\n\t\t\t});\n\t\t\thelper.appendChild(help);\n\t\t}\n\t}\n\n\treturn dialog;\n}\n\nLGraphCanvas.prototype.showEditPropertyValue = function( node, property, options )\n{\n\tif(!node || node.properties[ property ] === undefined )\n\t\treturn;\n\n\toptions = options || {};\n\tvar that = this;\n\n\tvar type = &quot;string&quot;;\n\n\tif(node.properties[ property ] !== null)\n\t\ttype = typeof(node.properties[ property ]);\n\n\t//for arrays\n\tif(type == &quot;object&quot;)\n\t{\n\t\tif( node.properties[ property ].length )\n\t\t\ttype = &quot;array&quot;;\n\t}\n\n\tvar info = null;\n\tif(node.getPropertyInfo)\n\t\tinfo = node.getPropertyInfo(property);\n\tif(node.properties_info)\n\t{\n\t\tfor(var i = 0; i &lt; node.properties_info.length; ++i)\n\t\t{\n\t\t\tif( node.properties_info[i].name == property )\n\t\t\t{\n\t\t\t\tinfo = node.properties_info[i];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif(info !== undefined &amp;&amp; info !== null &amp;&amp; info.type )\n\t\ttype = info.type;\n\n\tvar input_html = &quot;&quot;;\n\n\tif(type == &quot;string&quot; || type == &quot;number&quot; || type == &quot;array&quot;)\n\t\tinput_html = &quot;&lt;input autofocus type=&#x27;text&#x27; class=&#x27;value&#x27;/&gt;&quot;;\n\telse if(type == &quot;enum&quot; &amp;&amp; info.values)\n\t{\n\t\tinput_html = &quot;&lt;select autofocus type=&#x27;text&#x27; class=&#x27;value&#x27;&gt;&quot;;\n\t\tfor(var i in info.values)\n\t\t{\n\t\t\tvar v = info.values.constructor === Array ? info.values[i] : i;\n\t\t\tinput_html += &quot;&lt;option value=&#x27;&quot;+v+&quot;&#x27; &quot;+(v == node.properties[property] ? &quot;selected&quot; : &quot;&quot;)+&quot;&gt;&quot;+info.values[i]+&quot;&lt;/option&gt;&quot;;\n\t\t}\n\t\tinput_html += &quot;&lt;/select&gt;&quot;;\n\t}\n\telse if(type == &quot;boolean&quot;)\n\t{\n\t\tinput_html = &quot;&lt;input autofocus type=&#x27;checkbox&#x27; class=&#x27;value&#x27; &quot;+(node.properties[property] ? &quot;checked&quot; : &quot;&quot;)+&quot;/&gt;&quot;;\n\t}\n\telse\n\t{\n\t\tconsole.warn(&quot;unknown type: &quot; + type );\n\t\treturn;\n\t}\n\n\tvar dialog = this.createDialog( &quot;&lt;span class=&#x27;name&#x27;&gt;&quot; + property + &quot;&lt;/span&gt;&quot;+input_html+&quot;&lt;button&gt;OK&lt;/button&gt;&quot; , options );\n\n\tif(type == &quot;enum&quot; &amp;&amp; info.values)\n\t{\n\t\tvar input = dialog.querySelector(&quot;select&quot;);\n\t\tinput.addEventListener(&quot;change&quot;, function(e){\n\t\t\tsetValue( e.target.value );\n\t\t\t//var index = e.target.value;\n\t\t\t//setValue( e.options[e.selectedIndex].value );\n\t\t});\n\t}\n\telse if(type == &quot;boolean&quot;)\n\t{\n\t\tvar input = dialog.querySelector(&quot;input&quot;);\n\t\tif(input)\n\t\t{\n\t\t\tinput.addEventListener(&quot;click&quot;, function(e){\n\t\t\t\tsetValue( !!input.checked );\n\t\t\t});\n\t\t}\n\t}\n\telse\n\t{\n\t\tvar input = dialog.querySelector(&quot;input&quot;);\n\t\tif(input)\n\t\t{\n            input.addEventListener(&quot;blur&quot;, function(e){\n                this.focus();\n            });\n\t\t\tinput.value = node.properties[ property ] !== undefined ? node.properties[ property ] : &quot;&quot;;\n\t\t\tinput.addEventListener(&quot;keydown&quot;, function(e){\n\t\t\t\tif(e.keyCode != 13)\n\t\t\t\t\treturn;\n\t\t\t\tinner();\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\t\t\t});\n\t\t}\n\t}\n\n\tvar button = dialog.querySelector(&quot;button&quot;);\n\tbutton.addEventListener(&quot;click&quot;, inner );\n\n\tfunction inner()\n\t{\n\t\tsetValue( input.value );\n\t}\n\n\tfunction setValue(value)\n\t{\n\t\tif(typeof( node.properties[ property ] ) == &quot;number&quot;)\n\t\t\tvalue = Number(value);\n\t\tif(type == &quot;array&quot;)\n\t\t\tvalue = value.split(&quot;,&quot;).map(Number);\n\t\tnode.properties[ property ] = value;\n\t\tif(node._graph)\n\t\t\tnode._graph._version++;\n\t\tif(node.onPropertyChanged)\n\t\t\tnode.onPropertyChanged( property, value );\n\t\tdialog.close();\n\t\tnode.setDirtyCanvas(true,true);\n\t}\n}\n\nLGraphCanvas.prototype.createDialog = function( html, options )\n{\n\toptions = options || {};\n\n\tvar dialog = document.createElement(&quot;div&quot;);\n\tdialog.className = &quot;graphdialog&quot;;\n\tdialog.innerHTML = html;\n\n\tvar rect = this.canvas.getBoundingClientRect();\n\tvar offsetx = -20;\n\tvar offsety = -20;\n\tif(rect)\n\t{\n\t\toffsetx -= rect.left;\n\t\toffsety -= rect.top;\n\t}\n\n\tif( options.position )\n\t{\n\t\toffsetx += options.position[0];\n\t\toffsety += options.position[1];\n\t}\n\telse if( options.event )\n\t{\n\t\toffsetx += options.event.clientX;\n\t\toffsety += options.event.clientY;\n\t}\n\telse //centered\n\t{\n\t\toffsetx += this.canvas.width * 0.5;\n\t\toffsety += this.canvas.height * 0.5;\n\t}\n\n\tdialog.style.left = offsetx + &quot;px&quot;;\n\tdialog.style.top = offsety + &quot;px&quot;;\n\n\tthis.canvas.parentNode.appendChild( dialog );\n\n\tdialog.close = function()\n\t{\n\t\tif(this.parentNode)\n\t\t\tthis.parentNode.removeChild( this );\n\t}\n\n\treturn dialog;\n}\n\nLGraphCanvas.onMenuNodeCollapse = function( value, options, e, menu, node )\n{\n\tnode.collapse();\n}\n\nLGraphCanvas.onMenuNodePin = function( value, options, e, menu, node )\n{\n\tnode.pin();\n}\n\nLGraphCanvas.onMenuNodeMode = function( value, options, e, menu, node )\n{\n\tnew LiteGraph.ContextMenu([&quot;Always&quot;,&quot;On Event&quot;,&quot;On Trigger&quot;,&quot;Never&quot;], {event: e, callback: inner_clicked, parentMenu: menu, node: node });\n\n\tfunction inner_clicked(v)\n\t{\n\t\tif(!node)\n\t\t\treturn;\n\t\tswitch(v)\n\t\t{\n\t\t\tcase &quot;On Event&quot;: node.mode = LiteGraph.ON_EVENT; break;\n\t\t\tcase &quot;On Trigger&quot;: node.mode = LiteGraph.ON_TRIGGER; break;\n\t\t\tcase &quot;Never&quot;: node.mode = LiteGraph.NEVER; break;\n\t\t\tcase &quot;Always&quot;:\n\t\t\tdefault:\n\t\t\t\tnode.mode = LiteGraph.ALWAYS; break;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nLGraphCanvas.onMenuNodeColors = function( value, options, e, menu, node )\n{\n\tif(!node)\n\t\tthrow(&quot;no node for color&quot;);\n\n\tvar values = [];\n\tvalues.push({ value:null, content:&quot;&lt;span style=&#x27;display: block; padding-left: 4px;&#x27;&gt;No color&lt;/span&gt;&quot; });\n\n\tfor(var i in LGraphCanvas.node_colors)\n\t{\n\t\tvar color = LGraphCanvas.node_colors[i];\n\t\tvar value = { value:i, content:&quot;&lt;span style=&#x27;display: block; color: #999; padding-left: 4px; border-left: 8px solid &quot;+color.color+&quot;; background-color:&quot;+color.bgcolor+&quot;&#x27;&gt;&quot;+i+&quot;&lt;/span&gt;&quot; };\n\t\tvalues.push(value);\n\t}\n\tnew LiteGraph.ContextMenu( values, { event: e, callback: inner_clicked, parentMenu: menu, node: node });\n\n\tfunction inner_clicked(v)\n\t{\n\t\tif(!node)\n\t\t\treturn;\n\n\t\tvar color = v.value ? LGraphCanvas.node_colors[ v.value ] : null;\n\t\tif(color)\n\t\t{\n\t\t\tif(node.constructor === LiteGraph.LGraphGroup)\n\t\t\t\tnode.color = color.groupcolor;\n\t\t\telse\n\t\t\t{\n\t\t\t\tnode.color = color.color;\n\t\t\t\tnode.bgcolor = color.bgcolor;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdelete node.color;\n\t\t\tdelete node.bgcolor;\n\t\t}\n\t\tnode.setDirtyCanvas(true,true);\n\t}\n\n\treturn false;\n}\n\nLGraphCanvas.onMenuNodeShapes = function( value, options, e, menu, node )\n{\n\tif(!node)\n\t\tthrow(&quot;no node passed&quot;);\n\n\tnew LiteGraph.ContextMenu( LiteGraph.VALID_SHAPES, { event: e, callback: inner_clicked, parentMenu: menu, node: node });\n\n\tfunction inner_clicked(v)\n\t{\n\t\tif(!node)\n\t\t\treturn;\n\t\tnode.shape = v;\n\t\tnode.setDirtyCanvas(true);\n\t}\n\n\treturn false;\n}\n\nLGraphCanvas.onMenuNodeRemove = function( value, options, e, menu, node )\n{\n\tif(!node)\n\t\tthrow(&quot;no node passed&quot;);\n\n\tif(node.removable === false)\n\t\treturn;\n\n\tnode.graph.remove(node);\n\tnode.setDirtyCanvas(true,true);\n}\n\nLGraphCanvas.onMenuNodeClone = function( value, options, e, menu, node )\n{\n\tif(node.clonable == false) return;\n\tvar newnode = node.clone();\n\tif(!newnode)\n\t\treturn;\n\tnewnode.pos = [node.pos[0]+5,node.pos[1]+5];\n\tnode.graph.add(newnode);\n\tnode.setDirtyCanvas(true,true);\n}\n\nLGraphCanvas.node_colors = {\n\t&quot;red&quot;: { color:&quot;#322&quot;, bgcolor:&quot;#533&quot;, groupcolor: &quot;#A88&quot; },\n\t&quot;brown&quot;: { color:&quot;#332922&quot;, bgcolor:&quot;#593930&quot;, groupcolor: &quot;#b06634&quot; },\n\t&quot;green&quot;: { color:&quot;#232&quot;, bgcolor:&quot;#353&quot;, groupcolor: &quot;#8A8&quot; },\n\t&quot;blue&quot;: { color:&quot;#223&quot;, bgcolor:&quot;#335&quot;, groupcolor: &quot;#88A&quot; },\n\t&quot;pale_blue&quot;: { color:&quot;#2a363b&quot;, bgcolor:&quot;#3f5159&quot;, groupcolor: &quot;#3f789e&quot; },\n\t&quot;cyan&quot;: { color:&quot;#233&quot;, bgcolor:&quot;#355&quot;, groupcolor: &quot;#8AA&quot; },\n\t&quot;purple&quot;: { color:&quot;#323&quot;, bgcolor:&quot;#535&quot;, groupcolor: &quot;#a1309b&quot; },\n\t&quot;yellow&quot;: { color:&quot;#432&quot;, bgcolor:&quot;#653&quot;, groupcolor: &quot;#b58b2a&quot; },\n\t&quot;black&quot;: { color:&quot;#222&quot;, bgcolor:&quot;#000&quot;, groupcolor: &quot;#444&quot; }\n};\n\nLGraphCanvas.prototype.getCanvasMenuOptions = function()\n{\n\tvar options = null;\n\tif(this.getMenuOptions)\n\t\toptions = this.getMenuOptions();\n\telse\n\t{\n\t\toptions = [\n\t\t\t{ content:&quot;Add Node&quot;, has_submenu: true, callback: LGraphCanvas.onMenuAdd },\n\t\t\t{ content:&quot;Add Group&quot;, callback: LGraphCanvas.onGroupAdd }\n\t\t\t//{content:&quot;Collapse All&quot;, callback: LGraphCanvas.onMenuCollapseAll }\n\t\t];\n\n\t\tif(this._graph_stack &amp;&amp; this._graph_stack.length &gt; 0)\n\t\t\toptions.push(null,{content:&quot;Close subgraph&quot;, callback: this.closeSubgraph.bind(this) });\n\t}\n\n\tif(this.getExtraMenuOptions)\n\t{\n\t\tvar extra = this.getExtraMenuOptions(this,options);\n\t\tif(extra)\n\t\t\toptions = options.concat( extra );\n\t}\n\n\treturn options;\n}\n\n//called by processContextMenu to extract the menu list\nLGraphCanvas.prototype.getNodeMenuOptions = function( node )\n{\n\tvar options = null;\n\n\tif(node.getMenuOptions)\n\t\toptions = node.getMenuOptions(this);\n\telse\n\t\toptions = [\n\t\t\t{content:&quot;Inputs&quot;, has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalInputs },\n\t\t\t{content:&quot;Outputs&quot;, has_submenu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOptionalOutputs },\n\t\t\tnull,\n\t\t\t{content:&quot;Properties&quot;, has_submenu: true, callback: LGraphCanvas.onShowMenuNodeProperties },\n\t\t\tnull,\n\t\t\t{content:&quot;Title&quot;, callback: LGraphCanvas.onShowPropertyEditor },\n\t\t\t{content:&quot;Mode&quot;, has_submenu: true, callback: LGraphCanvas.onMenuNodeMode },\n\t\t\t{content:&quot;Resize&quot;, callback: LGraphCanvas.onResizeNode },\n\t\t\t{content:&quot;Collapse&quot;, callback: LGraphCanvas.onMenuNodeCollapse },\n\t\t\t{content:&quot;Pin&quot;, callback: LGraphCanvas.onMenuNodePin },\n\t\t\t{content:&quot;Colors&quot;, has_submenu: true, callback: LGraphCanvas.onMenuNodeColors },\n\t\t\t{content:&quot;Shapes&quot;, has_submenu: true, callback: LGraphCanvas.onMenuNodeShapes },\n\t\t\tnull\n\t\t];\n\n\tif(node.onGetInputs)\n\t{\n\t\tvar inputs = node.onGetInputs();\n\t\tif(inputs &amp;&amp; inputs.length)\n\t\t\toptions[0].disabled = false;\n\t}\n\n\tif(node.onGetOutputs)\n\t{\n\t\tvar outputs = node.onGetOutputs();\n\t\tif(outputs &amp;&amp; outputs.length )\n\t\t\toptions[1].disabled = false;\n\t}\n\n\tif(node.getExtraMenuOptions)\n\t{\n\t\tvar extra = node.getExtraMenuOptions(this);\n\t\tif(extra)\n\t\t{\n\t\t\textra.push(null);\n\t\t\toptions = extra.concat( options );\n\t\t}\n\t}\n\n\tif( node.clonable !== false )\n\t\t\toptions.push({content:&quot;Clone&quot;, callback: LGraphCanvas.onMenuNodeClone });\n\tif( node.removable !== false )\n\t\t\toptions.push(null,{content:&quot;Remove&quot;, callback: LGraphCanvas.onMenuNodeRemove });\n\n\tif(node.graph &amp;&amp; node.graph.onGetNodeMenuOptions )\n\t\tnode.graph.onGetNodeMenuOptions( options, node );\n\n\treturn options;\n}\n\nLGraphCanvas.prototype.getGroupMenuOptions = function( node )\n{\n\tvar o = [\n\t\t{content:&quot;Title&quot;, callback: LGraphCanvas.onShowPropertyEditor },\n\t\t{content:&quot;Color&quot;, has_submenu: true, callback: LGraphCanvas.onMenuNodeColors },\n\t\t{content:&quot;Font size&quot;, property: &quot;font_size&quot;, type:&quot;Number&quot;, callback: LGraphCanvas.onShowPropertyEditor },\n\t\tnull,\n\t\t{content:&quot;Remove&quot;, callback: LGraphCanvas.onMenuNodeRemove }\n\t];\n\n\treturn o;\n}\n\nLGraphCanvas.prototype.processContextMenu = function( node, event )\n{\n\tvar that = this;\n\tvar canvas = LGraphCanvas.active_canvas;\n\tvar ref_window = canvas.getCanvasWindow();\n\n\tvar menu_info = null;\n\tvar options = { event: event, callback: inner_option_clicked, extra: node };\n\n\t//check if mouse is in input\n\tvar slot = null;\n\tif(node)\n\t{\n\t\tslot = node.getSlotInPosition( event.canvasX, event.canvasY );\n\t\tLGraphCanvas.active_node = node;\n\t}\n\n\tif(slot) //on slot\n\t{\n\t\tmenu_info = [];\n\t\tif(slot &amp;&amp; slot.output &amp;&amp; slot.output.links &amp;&amp; slot.output.links.length)\n\t\t\tmenu_info.push( { content: &quot;Disconnect Links&quot;, slot: slot } );\n\t\tmenu_info.push( slot.locked ? &quot;Cannot remove&quot;  : { content: &quot;Remove Slot&quot;, slot: slot } );\n\t\tmenu_info.push( slot.nameLocked ? &quot;Cannot rename&quot; : { content: &quot;Rename Slot&quot;, slot: slot } );\n\t\toptions.title = (slot.input ? slot.input.type : slot.output.type) || &quot;*&quot;;\n\t\tif(slot.input &amp;&amp; slot.input.type == LiteGraph.ACTION)\n\t\t\toptions.title = &quot;Action&quot;;\n\t\tif(slot.output &amp;&amp; slot.output.type == LiteGraph.EVENT)\n\t\t\toptions.title = &quot;Event&quot;;\n\t}\n\telse\n\t{\n\t\tif( node ) //on node\n\t\t\tmenu_info = this.getNodeMenuOptions(node);\n\t\telse \n\t\t{\n\t\t\tmenu_info = this.getCanvasMenuOptions();\n\t\t\tvar group = this.graph.getGroupOnPos( event.canvasX, event.canvasY );\n\t\t\tif( group ) //on group\n\t\t\t\tmenu_info.push(null,{content:&quot;Edit Group&quot;, has_submenu: true, submenu: { title:&quot;Group&quot;, extra: group, options: this.getGroupMenuOptions( group ) }});\n\t\t}\n\t}\n\n\t//show menu\n\tif(!menu_info)\n\t\treturn;\n\n\tvar menu = new LiteGraph.ContextMenu( menu_info, options, ref_window );\n\n\tfunction inner_option_clicked( v, options, e )\n\t{\n\t\tif(!v)\n\t\t\treturn;\n\n\t\tif(v.content == &quot;Remove Slot&quot;)\n\t\t{\n\t\t\tvar info = v.slot;\n\t\t\tif(info.input)\n\t\t\t\tnode.removeInput( info.slot );\n\t\t\telse if(info.output)\n\t\t\t\tnode.removeOutput( info.slot );\n\t\t\treturn;\n\t\t}\n\t\telse if(v.content == &quot;Disconnect Links&quot;)\n\t\t{\n\t\t\tvar info = v.slot;\n\t\t\tif(info.output)\n\t\t\t\tnode.disconnectOutput( info.slot );\n\t\t\telse if(info.input)\n\t\t\t\tnode.disconnectInput( info.slot );\n\t\t\treturn;\n\t\t}\n\t\telse if( v.content == &quot;Rename Slot&quot;)\n\t\t{\n\t\t\tvar info = v.slot;\n            var slot_info = info.input ? node.getInputInfo( info.slot ) : node.getOutputInfo( info.slot );\n\t\t\tvar dialog = that.createDialog( &quot;&lt;span class=&#x27;name&#x27;&gt;Name&lt;/span&gt;&lt;input autofocus type=&#x27;text&#x27;/&gt;&lt;button&gt;OK&lt;/button&gt;&quot; , options );\n\t\t\tvar input = dialog.querySelector(&quot;input&quot;);\n\t\t\tif(input &amp;&amp; slot_info){\n\t\t\t\tinput.value = slot_info.label || &quot;&quot;;\n\t\t\t}\n\t\t\tdialog.querySelector(&quot;button&quot;).addEventListener(&quot;click&quot;,function(e){\n\t\t\t\tif(input.value)\n\t\t\t\t{\n\t\t\t\t\tif( slot_info )\n\t\t\t\t\t\tslot_info.label = input.value;\n\t\t\t\t\tthat.setDirty(true);\n\t\t\t\t}\n\t\t\t\tdialog.close();\n\t\t\t});\n\t\t}\n\n\t\t//if(v.callback)\n\t\t//\treturn v.callback.call(that, node, options, e, menu, that, event );\n\t}\n}\n\n\n\n\n\n\n//API *************************************************\n//like rect but rounded corners\nif(this.CanvasRenderingContext2D)\nCanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, radius_low) {\n  if ( radius === undefined ) {\n    radius = 5;\n  }\n\n  if(radius_low === undefined)\n\t radius_low  = radius;\n\n  this.moveTo(x + radius, y);\n  this.lineTo(x + width - radius, y);\n  this.quadraticCurveTo(x + width, y, x + width, y + radius);\n\n  this.lineTo(x + width, y + height - radius_low);\n  this.quadraticCurveTo(x + width, y + height, x + width - radius_low, y + height);\n  this.lineTo(x + radius_low, y + height);\n  this.quadraticCurveTo(x, y + height, x, y + height - radius_low);\n  this.lineTo(x, y + radius);\n  this.quadraticCurveTo(x, y, x + radius, y);\n}\n\nfunction compareObjects(a,b)\n{\n\tfor(var i in a)\n\t\tif(a[i] != b[i])\n\t\t\treturn false;\n\treturn true;\n}\nLiteGraph.compareObjects = compareObjects;\n\nfunction distance(a,b)\n{\n\treturn Math.sqrt( (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) );\n}\nLiteGraph.distance = distance;\n\nfunction colorToString(c)\n{\n\treturn &quot;rgba(&quot; + Math.round(c[0] * 255).toFixed() + &quot;,&quot; + Math.round(c[1] * 255).toFixed() + &quot;,&quot; + Math.round(c[2] * 255).toFixed() + &quot;,&quot; + (c.length == 4 ? c[3].toFixed(2) : &quot;1.0&quot;) + &quot;)&quot;;\n}\nLiteGraph.colorToString = colorToString;\n\nfunction isInsideRectangle( x,y, left, top, width, height)\n{\n\tif (left &lt; x &amp;&amp; (left + width) &gt; x &amp;&amp;\n\t\ttop &lt; y &amp;&amp; (top + height) &gt; y)\n\t\treturn true;\n\treturn false;\n}\nLiteGraph.isInsideRectangle = isInsideRectangle;\n\n//[minx,miny,maxx,maxy]\nfunction growBounding( bounding, x,y)\n{\n\tif(x &lt; bounding[0])\n\t\tbounding[0] = x;\n\telse if(x &gt; bounding[2])\n\t\tbounding[2] = x;\n\n\tif(y &lt; bounding[1])\n\t\tbounding[1] = y;\n\telse if(y &gt; bounding[3])\n\t\tbounding[3] = y;\n}\nLiteGraph.growBounding = growBounding;\n\n//point inside boundin box\nfunction isInsideBounding(p,bb)\n{\n\tif (p[0] &lt; bb[0][0] ||\n\t\tp[1] &lt; bb[0][1] ||\n\t\tp[0] &gt; bb[1][0] ||\n\t\tp[1] &gt; bb[1][1])\n\t\treturn false;\n\treturn true;\n}\nLiteGraph.isInsideBounding = isInsideBounding;\n\n//boundings overlap, format: [ startx, starty, width, height ]\nfunction overlapBounding(a,b)\n{\n\tvar A_end_x = a[0] + a[2];\n\tvar A_end_y = a[1] + a[3];\n\tvar B_end_x = b[0] + b[2];\n\tvar B_end_y = b[1] + b[3];\n\n\tif ( a[0] &gt; B_end_x ||\n\t\ta[1] &gt; B_end_y ||\n\t\tA_end_x &lt; b[0] ||\n\t\tA_end_y &lt; b[1])\n\t\treturn false;\n\treturn true;\n}\nLiteGraph.overlapBounding = overlapBounding;\n\n//Convert a hex value to its decimal value - the inputted hex must be in the\n//\tformat of a hex triplet - the kind we use for HTML colours. The function\n//\twill return an array with three values.\nfunction hex2num(hex) {\n\tif(hex.charAt(0) == &quot;#&quot;) hex = hex.slice(1); //Remove the &#x27;#&#x27; char - if there is one.\n\thex = hex.toUpperCase();\n\tvar hex_alphabets = &quot;0123456789ABCDEF&quot;;\n\tvar value = new Array(3);\n\tvar k = 0;\n\tvar int1,int2;\n\tfor(var i=0;i&lt;6;i+=2) {\n\t\tint1 = hex_alphabets.indexOf(hex.charAt(i));\n\t\tint2 = hex_alphabets.indexOf(hex.charAt(i+1));\n\t\tvalue[k] = (int1 * 16) + int2;\n\t\tk++;\n\t}\n\treturn(value);\n}\n\nLiteGraph.hex2num = hex2num;\n\n//Give a array with three values as the argument and the function will return\n//\tthe corresponding hex triplet.\nfunction num2hex(triplet) {\n\tvar hex_alphabets = &quot;0123456789ABCDEF&quot;;\n\tvar hex = &quot;#&quot;;\n\tvar int1,int2;\n\tfor(var i=0;i&lt;3;i++) {\n\t\tint1 = triplet[i] / 16;\n\t\tint2 = triplet[i] % 16;\n\n\t\thex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2);\n\t}\n\treturn(hex);\n}\n\nLiteGraph.num2hex = num2hex;\n\n/* LiteGraph GUI elements used for canvas editing *************************************/\n\n/**\n* ContextMenu from LiteGUI\n*\n* @class ContextMenu\n* @constructor\n* @param {Array} values (allows object { title: &quot;Nice text&quot;, callback: function ... })\n* @param {Object} options [optional] Some options:\\\n* - title: title to show on top of the menu\n* - callback: function to call when an option is clicked, it receives the item information\n* - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback\n* - event: you can pass a MouseEvent, this way the ContextMenu appears in that position\n*/\nfunction ContextMenu( values, options )\n{\n\toptions = options || {};\n\tthis.options = options;\n\tvar that = this;\n\n\t//to link a menu with its parent\n\tif(options.parentMenu)\n\t{\n\t\tif( options.parentMenu.constructor !== this.constructor )\n\t\t{\n\t\t\tconsole.error(&quot;parentMenu must be of class ContextMenu, ignoring it&quot;);\n\t\t\toptions.parentMenu = null;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis.parentMenu = options.parentMenu;\n\t\t\tthis.parentMenu.lock = true;\n\t\t\tthis.parentMenu.current_submenu = this;\n\t\t}\n\t}\n\n\tif(options.event &amp;&amp; options.event.constructor !== MouseEvent &amp;&amp; options.event.constructor !== CustomEvent)\n\t{\n\t\tconsole.error(&quot;Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it.&quot;);\n\t\toptions.event = null;\n\t}\n\n\tvar root = document.createElement(&quot;div&quot;);\n\troot.className = &quot;litegraph litecontextmenu litemenubar-panel&quot;;\n\tif( options.className) \n\t\troot.className += &quot; &quot; + options.className;\n\troot.style.minWidth = 100;\n\troot.style.minHeight = 100;\n\troot.style.pointerEvents = &quot;none&quot;;\n\tsetTimeout( function() { root.style.pointerEvents = &quot;auto&quot;; },100); //delay so the mouse up event is not caugh by this element\n\n\t//this prevents the default context browser menu to open in case this menu was created when pressing right button\n\troot.addEventListener(&quot;mouseup&quot;, function(e){\n\t\te.preventDefault(); return true;\n\t}, true);\n\troot.addEventListener(&quot;contextmenu&quot;, function(e) {\n\t\tif(e.button != 2) //right button\n\t\t\treturn false;\n\t\te.preventDefault();\n\t\treturn false;\n\t},true);\n\n\troot.addEventListener(&quot;mousedown&quot;, function(e){\n\t\tif(e.button == 2)\n\t\t{\n\t\t\tthat.close();\n\t\t\te.preventDefault(); return true;\n\t\t}\n\t}, true);\n\n\tfunction on_mouse_wheel(e)\n\t{\n\t\tvar pos = parseInt( root.style.top );\n\t\troot.style.top = (pos + e.deltaY * options.scroll_speed).toFixed() + &quot;px&quot;;\n\t\te.preventDefault();\n\t\treturn true;\n\t}\n\n\tif(!options.scroll_speed)\n\t\toptions.scroll_speed = 0.1;\n\n\troot.addEventListener(&quot;wheel&quot;, on_mouse_wheel, true);\n\troot.addEventListener(&quot;mousewheel&quot;, on_mouse_wheel, true);\n\n\n\tthis.root = root;\n\n\t//title\n\tif(options.title)\n\t{\n\t\tvar element = document.createElement(&quot;div&quot;);\n\t\telement.className = &quot;litemenu-title&quot;;\n\t\telement.innerHTML = options.title;\n\t\troot.appendChild(element);\n\t}\n\n\t//entries\n\tvar num = 0;\n\tfor(var i in values)\n\t{\n\t\tvar name = values.constructor == Array ? values[i] : i;\n\t\tif( name != null &amp;&amp; name.constructor !== String )\n\t\t\tname = name.content === undefined ? String(name) : name.content;\n\t\tvar value = values[i];\n\t\tthis.addItem( name, value, options );\n\t\tnum++;\n\t}\n\n\t//close on leave\n\troot.addEventListener(&quot;mouseleave&quot;, function(e) {\n\t\tif(that.lock)\n\t\t\treturn;\n\t\tif(root.closing_timer)\n\t\t\tclearTimeout( root.closing_timer );\n\t\troot.closing_timer = setTimeout( that.close.bind(that, e), 500 );\n\t\t//that.close(e);\n\t});\n\n\troot.addEventListener(&quot;mouseenter&quot;, function(e) {\n\t\tif(root.closing_timer)\n\t\t\tclearTimeout( root.closing_timer );\n\t});\n\n\t//insert before checking position\n\tvar root_document = document;\n\tif(options.event)\n\t\troot_document = options.event.target.ownerDocument;\n\n\tif(!root_document)\n\t\troot_document = document;\n\troot_document.body.appendChild(root);\n\n\t//compute best position\n\tvar left = options.left || 0;\n\tvar top = options.top || 0;\n\tif(options.event)\n\t{\n\t\tleft = (options.event.clientX - 10);\n\t\ttop = (options.event.clientY - 10);\n\t\tif(options.title)\n\t\t\ttop -= 20;\n\n\t\tif(options.parentMenu)\n\t\t{\n\t\t\tvar rect = options.parentMenu.root.getBoundingClientRect();\n\t\t\tleft = rect.left + rect.width;\n\t\t}\n\n\t\tvar body_rect = document.body.getBoundingClientRect();\n\t\tvar root_rect = root.getBoundingClientRect();\n\n\t\tif(left &gt; (body_rect.width - root_rect.width - 10))\n\t\t\tleft = (body_rect.width - root_rect.width - 10);\n\t\tif(top &gt; (body_rect.height - root_rect.height - 10))\n\t\t\ttop = (body_rect.height - root_rect.height - 10);\n\t}\n\n\troot.style.left = left + &quot;px&quot;;\n\troot.style.top = top  + &quot;px&quot;;\n\n\tif(options.scale)\n\t\troot.style.transform = &quot;scale(&quot;+options.scale+&quot;)&quot;;\n}\n\nContextMenu.prototype.addItem = function( name, value, options )\n{\n\tvar that = this;\n\toptions = options || {};\n\n\tvar element = document.createElement(&quot;div&quot;);\n\telement.className = &quot;litemenu-entry submenu&quot;;\n\n\tvar disabled = false;\n\n\tif(value === null)\n\t{\n\t\telement.classList.add(&quot;separator&quot;);\n\t\t//element.innerHTML = &quot;&lt;hr/&gt;&quot;\n\t\t//continue;\n\t}\n\telse\n\t{\n\t\telement.innerHTML = value &amp;&amp; value.title ? value.title : name;\n\t\telement.value = value;\n\n\t\tif(value)\n\t\t{\n\t\t\tif(value.disabled)\n\t\t\t{\n\t\t\t\tdisabled = true;\n\t\t\t\telement.classList.add(&quot;disabled&quot;);\n\t\t\t}\n\t\t\tif(value.submenu || value.has_submenu)\n\t\t\t\telement.classList.add(&quot;has_submenu&quot;);\n\t\t}\n\n\t\tif(typeof(value) == &quot;function&quot;)\n\t\t{\n\t\t\telement.dataset[&quot;value&quot;] = name;\n\t\t\telement.onclick_callback = value;\n\t\t}\n\t\telse\n\t\t\telement.dataset[&quot;value&quot;] = value;\n\n\t\tif(value.className)\n\t\t\telement.className += &quot; &quot; + value.className;\n\t}\n\n\tthis.root.appendChild(element);\n\tif(!disabled)\n\t\telement.addEventListener(&quot;click&quot;, inner_onclick);\n\tif(options.autoopen)\n\t\telement.addEventListener(&quot;mouseenter&quot;, inner_over);\n\n\tfunction inner_over(e)\n\t{\n\t\tvar value = this.value;\n\t\tif(!value || !value.has_submenu)\n\t\t\treturn;\n\t\t//if it is a submenu, autoopen like the item was clicked\n\t\tinner_onclick.call(this,e);\n\t}\n\n\t//menu option clicked\n\tfunction inner_onclick(e) {\n\t\tvar value = this.value;\n\t\tvar close_parent = true;\n\n\t\tif(that.current_submenu)\n\t\t\tthat.current_submenu.close(e);\n\n\t\t//global callback\n\t\tif(options.callback)\n\t\t{\n\t\t\tvar r = options.callback.call( this, value, options, e, that, options.node );\n\t\t\tif(r === true)\n\t\t\t\tclose_parent = false;\n\t\t}\n\n\t\t//special cases\n\t\tif(value)\n\t\t{\n\t\t\tif (value.callback &amp;&amp; !options.ignore_item_callbacks &amp;&amp; value.disabled !== true )  //item callback\n\t\t\t{\n\t\t\t\tvar r = value.callback.call( this, value, options, e, that, options.extra );\n\t\t\t\tif(r === true)\n\t\t\t\t\tclose_parent = false;\n\t\t\t}\n\t\t\tif(value.submenu)\n\t\t\t{\n\t\t\t\tif(!value.submenu.options)\n\t\t\t\t\tthrow(&quot;ContextMenu submenu needs options&quot;);\n\t\t\t\tvar submenu = new that.constructor( value.submenu.options, {\n\t\t\t\t\tcallback: value.submenu.callback,\n\t\t\t\t\tevent: e,\n\t\t\t\t\tparentMenu: that,\n\t\t\t\t\tignore_item_callbacks: value.submenu.ignore_item_callbacks,\n\t\t\t\t\ttitle: value.submenu.title,\n\t\t\t\t\textra: value.submenu.extra,\n\t\t\t\t\tautoopen: options.autoopen\n\t\t\t\t});\n\t\t\t\tclose_parent = false;\n\t\t\t}\n\t\t}\n\n\t\tif(close_parent &amp;&amp; !that.lock)\n\t\t\tthat.close();\n\t}\n\n\treturn element;\n}\n\nContextMenu.prototype.close = function(e, ignore_parent_menu)\n{\n\tif(this.root.parentNode)\n\t\tthis.root.parentNode.removeChild( this.root );\n\tif(this.parentMenu &amp;&amp; !ignore_parent_menu)\n\t{\n\t\tthis.parentMenu.lock = false;\n\t\tthis.parentMenu.current_submenu = null;\n\t\tif( e === undefined )\n\t\t\tthis.parentMenu.close();\n\t\telse if( e &amp;&amp; !ContextMenu.isCursorOverElement( e, this.parentMenu.root) )\n\t\t{\n\t\t\tContextMenu.trigger( this.parentMenu.root, &quot;mouseleave&quot;, e );\n\t\t}\n\t}\n\tif(this.current_submenu)\n\t\tthis.current_submenu.close(e, true);\n\n\tif(this.root.closing_timer)\n\t\tclearTimeout( this.root.closing_timer );\n}\n\n//this code is used to trigger events easily (used in the context menu mouseleave\nContextMenu.trigger = function( element, event_name, params, origin )\n{\n\tvar evt = document.createEvent( &#x27;CustomEvent&#x27; );\n\tevt.initCustomEvent( event_name, true,true, params ); //canBubble, cancelable, detail\n\tevt.srcElement = origin;\n\tif( element.dispatchEvent )\n\t\telement.dispatchEvent( evt );\n\telse if( element.__events )\n\t\telement.__events.dispatchEvent( evt );\n\t//else nothing seems binded here so nothing to do\n\treturn evt;\n}\n\n//returns the top most menu\nContextMenu.prototype.getTopMenu = function()\n{\n\tif( this.options.parentMenu )\n\t\treturn this.options.parentMenu.getTopMenu();\n\treturn this;\n}\n\nContextMenu.prototype.getFirstEvent = function()\n{\n\tif( this.options.parentMenu )\n\t\treturn this.options.parentMenu.getFirstEvent();\n\treturn this.options.event;\n}\n\n\n\nContextMenu.isCursorOverElement = function( event, element )\n{\n\tvar left = event.clientX;\n\tvar top = event.clientY;\n\tvar rect = element.getBoundingClientRect();\n\tif(!rect)\n\t\treturn false;\n\tif(top &gt; rect.top &amp;&amp; top &lt; (rect.top + rect.height) &amp;&amp;\n\t\tleft &gt; rect.left &amp;&amp; left &lt; (rect.left + rect.width) )\n\t\treturn true;\n\treturn false;\n}\n\n\n\nLiteGraph.ContextMenu = ContextMenu;\n\nLiteGraph.closeAllContextMenus = function( ref_window )\n{\n\tref_window = ref_window || window;\n\n\tvar elements = ref_window.document.querySelectorAll(&quot;.litecontextmenu&quot;);\n\tif(!elements.length)\n\t\treturn;\n\n\tvar result = [];\n\tfor(var i = 0; i &lt; elements.length; i++)\n\t\tresult.push(elements[i]);\n\n\tfor(var i in result)\n\t{\n\t\tif(result[i].close)\n\t\t\tresult[i].close();\n\t\telse if(result[i].parentNode)\n\t\t\tresult[i].parentNode.removeChild( result[i] );\n\t}\n}\n\nLiteGraph.extendClass = function ( target, origin )\n{\n\tfor(var i in origin) //copy class properties\n\t{\n\t\tif(target.hasOwnProperty(i))\n\t\t\tcontinue;\n\t\ttarget[i] = origin[i];\n\t}\n\n\tif(origin.prototype) //copy prototype properties\n\t\tfor(var i in origin.prototype) //only enumerables\n\t\t{\n\t\t\tif(!origin.prototype.hasOwnProperty(i))\n\t\t\t\tcontinue;\n\n\t\t\tif(target.prototype.hasOwnProperty(i)) //avoid overwritting existing ones\n\t\t\t\tcontinue;\n\n\t\t\t//copy getters\n\t\t\tif(origin.prototype.__lookupGetter__(i))\n\t\t\t\ttarget.prototype.__defineGetter__(i, origin.prototype.__lookupGetter__(i));\n\t\t\telse\n\t\t\t\ttarget.prototype[i] = origin.prototype[i];\n\n\t\t\t//and setters\n\t\t\tif(origin.prototype.__lookupSetter__(i))\n\t\t\t\ttarget.prototype.__defineSetter__(i, origin.prototype.__lookupSetter__(i));\n\t\t}\n}\n\n//used to create nodes from wrapping functions\nLiteGraph.getParameterNames = function(func) {\n    return (func + &#x27;&#x27;)\n      .replace(/[/][/].*$/mg,&#x27;&#x27;) // strip single-line comments\n      .replace(/\\s+/g, &#x27;&#x27;) // strip white space\n      .replace(/[/][*][^/*]*[*][/]/g, &#x27;&#x27;) // strip multi-line comments  /**/\n      .split(&#x27;){&#x27;, 1)[0].replace(/^[^(]*[(]/, &#x27;&#x27;) // extract the parameters\n      .replace(/=[^,]+/g, &#x27;&#x27;) // strip any ES6 defaults\n      .split(&#x27;,&#x27;).filter(Boolean); // split &amp; filter [&quot;&quot;]\n}\n\nMath.clamp = function(v,a,b) { return (a &gt; v ? a : (b &lt; v ? b : v)); }\n\nif( typeof(window) != &quot;undefined&quot; &amp;&amp; !window[&quot;requestAnimationFrame&quot;] )\n{\n\twindow.requestAnimationFrame = window.webkitRequestAnimationFrame ||\n\t\t  window.mozRequestAnimationFrame    ||\n\t\t  (function( callback ){\n\t\t\twindow.setTimeout(callback, 1000 / 60);\n\t\t  });\n}\n\n})(this);\n\nif(typeof(exports) != &quot;undefined&quot;)\n\texports.LiteGraph = this.LiteGraph;\n\n    </pre>\n</div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n<script src=\"../assets/vendor/prettify/prettify-min.js\"></script>\n<script>prettyPrint();</script>\n<script src=\"../assets/js/yui-prettify.js\"></script>\n<script src=\"../assets/../api.js\"></script>\n<script src=\"../assets/js/api-filter.js\"></script>\n<script src=\"../assets/js/api-list.js\"></script>\n<script src=\"../assets/js/api-search.js\"></script>\n<script src=\"../assets/js/apidocs.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "doc/files/index.html",
    "content": "<!doctype html>\n<html>\n    <head>\n        <title>Redirector</title>\n        <meta http-equiv=\"refresh\" content=\"0;url=../\">\n    </head>\n    <body>\n        <a href=\"../\">Click here to redirect</a>\n    </body>\n</html>\n"
  },
  {
    "path": "doc/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <title></title>\n    <link rel=\"stylesheet\" href=\"http://yui.yahooapis.com/3.9.1/build/cssgrids/cssgrids-min.css\">\n    <link rel=\"stylesheet\" href=\"./assets/vendor/prettify/prettify-min.css\">\n    <link rel=\"stylesheet\" href=\"./assets/css/main.css\" id=\"site_styles\">\n    <link rel=\"icon\" href=\"./assets/favicon.ico\">\n    <script src=\"http://yui.yahooapis.com/combo?3.9.1/build/yui/yui-min.js\"></script>\n</head>\n<body class=\"yui3-skin-sam\">\n\n<div id=\"doc\">\n    <div id=\"hd\" class=\"yui3-g header\">\n        <div class=\"yui3-u-3-4\">\n                <h1><img src=\"./assets/css/logo.png\" title=\"\" width=\"117\" height=\"52\"></h1>\n        </div>\n        <div class=\"yui3-u-1-4 version\">\n            <em>API Docs for: </em>\n        </div>\n    </div>\n    <div id=\"bd\" class=\"yui3-g\">\n\n        <div class=\"yui3-u-1-4\">\n            <div id=\"docs-sidebar\" class=\"sidebar apidocs\">\n                <div id=\"api-list\">\n                    <h2 class=\"off-left\">APIs</h2>\n                    <div id=\"api-tabview\" class=\"tabview\">\n                        <ul class=\"tabs\">\n                            <li><a href=\"#api-classes\">Classes</a></li>\n                            <li><a href=\"#api-modules\">Modules</a></li>\n                        </ul>\n                \n                        <div id=\"api-tabview-filter\">\n                            <input type=\"search\" id=\"api-filter\" placeholder=\"Type to filter APIs\">\n                        </div>\n                \n                        <div id=\"api-tabview-panel\">\n                            <ul id=\"api-classes\" class=\"apis classes\">\n                                <li><a href=\"./classes/ContextMenu.html\">ContextMenu</a></li>\n                                <li><a href=\"./classes/LGraph.html\">LGraph</a></li>\n                                <li><a href=\"./classes/LGraphCanvas.html\">LGraphCanvas</a></li>\n                                <li><a href=\"./classes/LGraphNode.html\">LGraphNode</a></li>\n                                <li><a href=\"./classes/LiteGraph.html\">LiteGraph</a></li>\n                            </ul>\n                \n                \n                            <ul id=\"api-modules\" class=\"apis modules\">\n                            </ul>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <div class=\"yui3-u-3-4\">\n                <div id=\"api-options\">\n                    Show:\n                    <label for=\"api-show-inherited\">\n                        <input type=\"checkbox\" id=\"api-show-inherited\" checked>\n                        Inherited\n                    </label>\n            \n                    <label for=\"api-show-protected\">\n                        <input type=\"checkbox\" id=\"api-show-protected\">\n                        Protected\n                    </label>\n            \n                    <label for=\"api-show-private\">\n                        <input type=\"checkbox\" id=\"api-show-private\">\n                        Private\n                    </label>\n                    <label for=\"api-show-deprecated\">\n                        <input type=\"checkbox\" id=\"api-show-deprecated\">\n                        Deprecated\n                    </label>\n            \n                </div>\n            \n            <div class=\"apidocs\">\n                <div id=\"docs-main\">\n                    <div class=\"content\">\n    <div class=\"apidocs\">\n        <div id=\"docs-main\" class=\"content\">\n            <p>\n            Browse to a module or class using the sidebar to view its API documentation.\n            </p>\n\n            <h2>Keyboard Shortcuts</h2>\n\n            <ul>\n                <li><p>Press <kbd>s</kbd> to focus the API search box.</p></li>\n\n                <li><p>Use <kbd>Up</kbd> and <kbd>Down</kbd> to select classes, modules, and search results.</p></li>\n\n                <li class=\"mac-only\"><p>With the API search box or sidebar focused, use <kbd><span class=\"cmd\">&#x2318;</span>-Left</kbd> or <kbd><span class=\"cmd\">&#x2318;</span>-Right</kbd> to switch sidebar tabs.</p></li>\n\n                <li class=\"pc-only\"><p>With the API search box or sidebar focused, use <kbd>Ctrl+Left</kbd> and <kbd>Ctrl+Right</kbd> to switch sidebar tabs.</p></li>\n            </ul>\n        </div>\n    </div>\n\n\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n<script src=\"./assets/vendor/prettify/prettify-min.js\"></script>\n<script>prettyPrint();</script>\n<script src=\"./assets/js/yui-prettify.js\"></script>\n<script src=\"./assets/../api.js\"></script>\n<script src=\"./assets/js/api-filter.js\"></script>\n<script src=\"./assets/js/api-list.js\"></script>\n<script src=\"./assets/js/api-search.js\"></script>\n<script src=\"./assets/js/apidocs.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "doc/modules/index.html",
    "content": "<!doctype html>\n<html>\n    <head>\n        <title>Redirector</title>\n        <meta http-equiv=\"refresh\" content=\"0;url=../\">\n    </head>\n    <body>\n        <a href=\"../\">Click here to redirect</a>\n    </body>\n</html>\n"
  },
  {
    "path": "editor/editor_mobile.html",
    "content": "<!-- Javi Agenjo (@tamat) on 31/9/2011 -->\n<!DOCTYPE html>\n<html>\n<head>\n    <title>LiteGraph</title>\n\t<!--<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">-->\n\t<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"../css/litegraph.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"../css/litegraph-editor.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">\n    \n</head>\n<body>\n\t<div id=\"main\">\n\t</div>\n\n    <script type=\"text/javascript\" src=\"../external/jquery-1.6.2.min.js\"></script>\n    <script type=\"text/javascript\" src=\"https://tamats.com/projects/sillyserver/src/sillyclient.js\"></script>\n\t<!-- <script type=\"text/javascript\" src=\"https://unpkg.com/codeflask/build/codeflask.min.js\"></script> -->\n\t<script type=\"text/javascript\" src=\"js/libs/gl-matrix-min.js\"></script>\n    <script type=\"text/javascript\" src=\"js/libs/audiosynth.js\"></script>\n    <script type=\"text/javascript\" src=\"js/libs/midi-parser.js\"></script>\n\n    <script type=\"text/javascript\" src=\"../src/litegraph.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/litegraph-editor.js\"></script>\n    <script type=\"text/javascript\" src=\"js/defaults_mobile.js\"></script>\n\n    <script type=\"text/javascript\" src=\"../src/nodes/base.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/logic.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/events.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/math.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/math3d.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/strings.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/interface.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/geometry.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/graphics.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/input.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/midi.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/audio.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/network.js\"></script>\n\n    <script type=\"text/javascript\" src=\"js/demos.js\"></script>\n\t<script type=\"text/javascript\" src=\"js/code.js\"></script>\n\n\t<script type=\"text/javascript\" src=\"../src/nodes/others.js\"></script>\n\n\t<!--  htmlConsole use to debug on mobile, include and set editorUseHtmlConsole in defaults.js -->\n\t<!-- enable console style, html, js enabling/disabling this comment here->  -->\n\t\t\n\t    <link rel=\"stylesheet\" href=\"//htmlacademy.github.io/console.js/latest/css/style.css\">\n\t    <style>\n\t    \t.invisible{ display: none; }\n\t    \t.console__row{\n\t    \t\tmargin: 1px;\n\t\t\t\tpadding: 2px;\n\t\t\t}\n\t\t\t.console-container{\n\t\t\t\tmin-width: 200px;\n\t\t\t\tbackground: rgba(255,255,255,0.1);\n\t\t\t\tposition: fixed;\n\t\t\t\ttop: 38px;\n\t\t\t\tleft: 0;\n\t\t\t\toverflow: auto;\n\t\t\t\theight: calc(100%-38px);\n\t\t\t}\n\t\t\t.console-container.small{\n\t\t\t\tmax-width: 30%;\n\t\t\t}\n\t\t\t.graphcanvas{\n\t\t\t\t/*WONT WORK touch-action: manipulation;*/\n\t\t\t\t/*touch-action: none;*/\n\t\t\t\ttouch-action: pinch-zoom;\n\t\t\t}\n\t    </style>\n\t\t\n\t\t<div id=\"console-container\" class=\"litegraph-editor console-container small invisible\" style=\"\">\n\t\t\t<div class=\"console-tools\" style=\"position: absolute; top: 0; right:0; z-index:2;\">\n\t\t\t\t<button class='btn' id='btn_console_close'>close</button>\n\t\t\t\t<button class='btn' id='btn_console_clear'>clear</button>\n\t\t\t</div>\n\t\t</div>\n\t\t\n\t\t<script src=\"//htmlacademy.github.io/console.js/latest/js/index.js\"></script>\n\t\t<script>\n\t\t\n\t\t\tvar editorUseHtmlConsole = true; // enable html console to debug on mobile\n\t\t\n\t\t\t// ToBarSelector\n\t\t\tif(editorUseHtmlConsole){\n\t\t\t\tdocument.getElementById(\"LGEditorTopBarSelector\").innerHTML = \"<button class='btn' id='btn_console'>Console</button> \"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+document.getElementById(\"LGEditorTopBarSelector\").innerHTML;\n\t\t\t}\n\t\t\t\n\t\t\t// html console\n\t\t\tif(editorUseHtmlConsole){\n\t\t\t\telem.querySelector(\"#btn_console\").addEventListener(\"click\", function(){\n\t\t\t\t\tvar consoleCnt = document.getElementById('console-container');\n\t\t\t\t\tif (consoleCnt.classList.contains(\"invisible\")){\n\t\t\t\t\t\tconsoleCnt.classList.remove(\"invisible\");\n\t\t\t\t\t}else{\n\t\t\t\t\t\tjsConsole.clean();\n\t\t\t\t\t\tconsoleCnt.classList.add(\"invisible\");\n\t\t\t\t\t}\t\n\t\t\t\t});\n\t\t\t\t\n\t\t\t\n\t\t\t\tconst params = {\n\t\t\t\t\texpandDepth : 1,\n\t\t\t\t\tcommon : {\n\t\t\t\t\t\texcludeProperties : ['__proto__'],\n\t\t\t\t\t\tremoveProperties: ['__proto__'],\n\t\t\t\t\t\tmaxFieldsInHead : 5,\n\t\t\t\t\t\tminFieldsToAutoexpand : 5,\n\t\t\t\t\t\tmaxFieldsToAutoexpand : 15\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tvar jsConsole = new Console(document.querySelector('.console-container'), params);\n\t\t\t\tjsConsole.log(\"Here is console.log!\");\n\t\t\t    \n\t\t\t\t// map console log-debug to jsConsole\n\t\t\t\tconsole.log = function(par){\n\t\t\t    \tjsConsole.log(par);\n\t\t\t    \tvar objDiv = document.getElementById(\"console-container\");\n\t\t\t\t\tobjDiv.scrollTop = objDiv.scrollHeight;\n\t\t\t    }\n\t\t\t    console.debug = console.log;\n\t\t\t    \n\t\t\t    console.log(\"going into html console\");\n\t\t\t    \n\t\t\t    document.getElementById(\"btn_console_clear\").addEventListener(\"click\", function(){\n\t\t\t    \tvar consoleCnt = document.getElementById('console-container');\n\t\t\t    \tjsConsole.clean();\n\t\t\t    });\n\t\t\t    document.getElementById(\"btn_console_close\").addEventListener(\"click\", function(){\n\t\t\t    \tvar consoleCnt = document.getElementById('console-container');\n\t\t\t    \tconsoleCnt.classList.add(\"invisible\");\n\t\t\t    });\n\t\t\t}\n\t\t</script>\n\t<!--  -->\n\n</body>\n</html>\n"
  },
  {
    "path": "editor/examples/audio.json",
    "content": "{\"last_node_id\":16,\"last_link_id\":16,\"nodes\":[{\"id\":9,\"type\":\"widget/knob\",\"pos\":[440,81],\"size\":[80,100],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[8,9]}],\"properties\":{\"min\":0,\"max\":4,\"value\":0.24000000000000002,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":10,\"type\":\"basic/watch\",\"pos\":[537,81],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"value\",\"type\":0,\"link\":9,\"label\":\"0.000\"}],\"outputs\":[{\"name\":\"value\",\"type\":0,\"links\":null,\"label\":\"\"}],\"properties\":{\"value\":0.24000000000000002}},{\"id\":1,\"type\":\"audio/destination\",\"pos\":[699,83],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":\"audio\",\"link\":1}],\"properties\":{}},{\"id\":6,\"type\":\"widget/knob\",\"pos\":[116,179],\"size\":[80,100],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[5]}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.5099999999999996,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":0,\"type\":\"audio/source\",\"pos\":[272,192],\"size\":{\"0\":140,\"1\":86},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"gain\",\"type\":\"number\",\"link\":5},{\"name\":\"Play\",\"type\":-1,\"link\":6},{\"name\":\"Stop\",\"type\":-1,\"link\":7},{\"name\":\"playbackRate\",\"type\":\"number\",\"link\":8}],\"outputs\":[{\"name\":\"out\",\"type\":\"audio\",\"links\":[0]}],\"properties\":{\"src\":\"demodata/audio.wav\",\"gain\":0.5,\"loop\":true,\"autoplay\":true,\"playbackRate\":0.24000000000000002},\"boxcolor\":\"#AA4\"},{\"id\":2,\"type\":\"audio/biquadfilter\",\"pos\":[442,228],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":\"audio\",\"link\":0},{\"name\":\"frequency\",\"type\":\"number\",\"link\":4}],\"outputs\":[{\"name\":\"out\",\"type\":\"audio\",\"links\":[1,2]}],\"properties\":{\"frequency\":350,\"detune\":0,\"Q\":1,\"type\":\"lowpass\"}},{\"id\":3,\"type\":\"audio/analyser\",\"pos\":[704,231],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":\"audio\",\"link\":2}],\"outputs\":[{\"name\":\"freqs\",\"type\":\"array\",\"links\":[3,10]},{\"name\":\"samples\",\"type\":\"array\",\"links\":null}],\"properties\":{\"fftSize\":2048,\"minDecibels\":-100,\"maxDecibels\":-10,\"smoothingTimeConstant\":0.5}},{\"id\":11,\"type\":\"audio/signal\",\"pos\":[882,395],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"freqs\",\"type\":\"array\",\"link\":10},{\"name\":\"band\",\"type\":\"number\",\"link\":11}],\"outputs\":[{\"name\":\"signal\",\"type\":\"number\",\"links\":[12]}],\"properties\":{\"band\":440,\"amplitude\":1,\"samplerate\":44100}},{\"id\":4,\"type\":\"audio/visualization\",\"pos\":[885,503],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"freqs\",\"type\":\"array\",\"link\":3},{\"name\":\"mark\",\"type\":\"number\",\"link\":13}],\"properties\":{\"continuous\":true,\"mark\":12000.000000000005,\"samplerate\":44100}},{\"id\":5,\"type\":\"widget/knob\",\"pos\":[112,314],\"size\":[80,100],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[4]}],\"properties\":{\"min\":0,\"max\":20000,\"value\":14800.00000000001,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":13,\"type\":\"basic/watch\",\"pos\":[110,458],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"value\",\"type\":0,\"link\":12,\"label\":\"0.000\"}],\"outputs\":[{\"name\":\"value\",\"type\":0,\"links\":[14],\"label\":\"\"}],\"title\":\"Max. Signal\",\"properties\":{\"value\":0.3843137254901945}},{\"id\":14,\"type\":\"widget/progress\",\"pos\":[300,460],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"\",\"type\":\"number\",\"link\":14}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.3843137254901945,\"wcolor\":\"#AAF\"}},{\"id\":12,\"type\":\"widget/knob\",\"pos\":[460,458],\"size\":[80,100],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[11,13,15]}],\"properties\":{\"min\":0,\"max\":24000,\"value\":12000.000000000005,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":15,\"type\":\"basic/watch\",\"pos\":[888,598],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"value\",\"type\":0,\"link\":15,\"label\":\"0.000\"}],\"outputs\":[{\"name\":\"value\",\"type\":0,\"links\":null,\"label\":\"\"}],\"properties\":{\"value\":12000.000000000005}},{\"id\":7,\"type\":\"widget/button\",\"pos\":[113,83],\"size\":{\"0\":141,\"1\":60},\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"clicked\",\"type\":-1,\"links\":[6]}],\"properties\":{\"text\":\"Play\",\"font_size\":30,\"message\":\"\",\"font\":\"40px Arial\"}},{\"id\":8,\"type\":\"widget/button\",\"pos\":[273,83],\"size\":{\"0\":143,\"1\":62},\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"clicked\",\"type\":-1,\"links\":[7]}],\"properties\":{\"text\":\"Stop\",\"font_size\":30,\"message\":\"\",\"font\":\"40px Arial\"}}],\"links\":[[0,0,0,2,0,null],[1,2,0,1,0,null],[2,2,0,3,0,null],[3,3,0,4,0,null],[4,5,0,2,1,null],[5,6,0,0,0,null],[6,7,0,0,1,null],[7,8,0,0,2,null],[8,9,0,0,3,null],[9,9,0,10,0,null],[10,3,0,11,0,null],[11,12,0,11,1,null],[12,11,0,13,0,null],[13,12,0,4,1,null],[14,13,0,14,0,null],[15,12,0,15,0,null]],\"groups\":[],\"config\":{}}"
  },
  {
    "path": "editor/examples/audio_delay.json",
    "content": "{\"last_node_id\":7,\"last_link_id\":7,\"nodes\":[{\"id\":6,\"type\":\"widget/knob\",\"pos\":[199,296],\"size\":[64,84],\"flags\":{},\"order\":3,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[6]}],\"properties\":{\"min\":0,\"max\":2,\"value\":0.8799999999999999,\"color\":\"#7AF\",\"precision\":2,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(112,112,112,1.0)\"},{\"id\":0,\"type\":\"audio/source\",\"pos\":[195,187],\"size\":{\"0\":137,\"1\":33},\"flags\":{},\"order\":0,\"mode\":0,\"inputs\":[{\"name\":\"gain\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"out\",\"type\":\"audio\",\"links\":[0,1]}],\"properties\":{\"src\":\"demodata/audio.wav\",\"gain\":0.5,\"loop\":true,\"autoplay\":true,\"playbackRate\":1},\"boxcolor\":\"#AA4\"},{\"id\":4,\"type\":\"widget/knob\",\"pos\":[408,59],\"size\":[82,75],\"flags\":{},\"order\":1,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[4]}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.24000000000000007,\"color\":\"#7AF\",\"precision\":2,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":2,\"type\":\"audio/delay\",\"pos\":[385,255],\"size\":{\"0\":143,\"1\":49},\"flags\":{},\"order\":4,\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":\"audio\",\"link\":1},{\"name\":\"time\",\"type\":\"number\",\"link\":6}],\"outputs\":[{\"name\":\"out\",\"type\":\"audio\",\"links\":[2]}],\"properties\":{\"delayTime\":0.5,\"time\":1}},{\"id\":5,\"type\":\"widget/knob\",\"pos\":[433,371],\"size\":[79,79],\"flags\":{},\"order\":2,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[5]}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.5199999999999996,\"color\":\"#7AF\",\"precision\":2,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":1,\"type\":\"audio/mixer\",\"pos\":[657,180],\"size\":{\"0\":147,\"1\":91},\"flags\":{},\"order\":5,\"mode\":0,\"inputs\":[{\"name\":\"in1\",\"type\":\"audio\",\"link\":0},{\"name\":\"in1 gain\",\"type\":\"number\",\"link\":4},{\"name\":\"in2\",\"type\":\"audio\",\"link\":2},{\"name\":\"in2 gain\",\"type\":\"number\",\"link\":5}],\"outputs\":[{\"name\":\"out\",\"type\":\"audio\",\"links\":[3]}],\"properties\":{\"gain1\":0.5,\"gain2\":0.8}},{\"id\":3,\"type\":\"audio/destination\",\"pos\":[911,180],\"size\":{\"0\":145,\"1\":30},\"flags\":{},\"order\":6,\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":\"audio\",\"link\":3}],\"properties\":{}}],\"links\":[[0,0,0,1,0,null],[1,0,0,2,0,null],[2,2,0,1,2,null],[3,1,0,3,0,null],[4,4,0,1,1,null],[5,5,0,1,3,null],[6,6,0,2,1,null]],\"groups\":[],\"config\":{},\"version\":0.4}"
  },
  {
    "path": "editor/examples/audio_reverb.json",
    "content": "{\"last_node_id\":8,\"last_link_id\":9,\"nodes\":[{\"id\":4,\"type\":\"widget/knob\",\"pos\":[408,59],\"size\":[81,93],\"flags\":{},\"order\":2,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[4]}],\"title\":\"Main\",\"properties\":{\"min\":0,\"max\":1,\"value\":0.21000000000000002,\"color\":\"#7AF\",\"precision\":2,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":5,\"type\":\"widget/knob\",\"pos\":[398,350],\"size\":[84,100],\"flags\":{},\"order\":1,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[5]}],\"title\":\"Reverb\",\"properties\":{\"min\":0,\"max\":1,\"value\":0.4099999999999999,\"color\":\"#7AF\",\"precision\":2,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":7,\"type\":\"audio/convolver\",\"pos\":[421,253],\"size\":{\"0\":140,\"1\":31},\"flags\":{},\"order\":3,\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":\"audio\",\"link\":7}],\"outputs\":[{\"name\":\"out\",\"type\":\"audio\",\"links\":[8]}],\"properties\":{\"impulse_src\":\"demodata/impulse.wav\",\"normalize\":true}},{\"id\":1,\"type\":\"audio/mixer\",\"pos\":[655,183],\"size\":{\"0\":145,\"1\":94},\"flags\":{},\"order\":4,\"mode\":0,\"inputs\":[{\"name\":\"in1\",\"type\":\"audio\",\"link\":0},{\"name\":\"in1 gain\",\"type\":\"number\",\"link\":4},{\"name\":\"in2\",\"type\":\"audio\",\"link\":8},{\"name\":\"in2 gain\",\"type\":\"number\",\"link\":5}],\"outputs\":[{\"name\":\"out\",\"type\":\"audio\",\"links\":[3]}],\"properties\":{\"gain1\":0.5,\"gain2\":0.8}},{\"id\":3,\"type\":\"audio/destination\",\"pos\":[911,180],\"size\":{\"0\":141,\"1\":34},\"flags\":{},\"order\":5,\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":\"audio\",\"link\":3}],\"properties\":{}},{\"id\":0,\"type\":\"audio/source\",\"pos\":[195,187],\"size\":{\"0\":143,\"1\":30},\"flags\":{},\"order\":0,\"mode\":0,\"inputs\":[{\"name\":\"gain\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"out\",\"type\":\"audio\",\"links\":[0,7]}],\"properties\":{\"src\":\"demodata/audio.wav\",\"gain\":0.5,\"loop\":true,\"autoplay\":true,\"playbackRate\":1},\"boxcolor\":\"#AA4\"}],\"links\":[[0,0,0,1,0,null],[1,0,0,2,0,null],[3,1,0,3,0,null],[4,4,0,1,1,null],[5,5,0,1,3,null],[6,6,0,2,1,null],[7,0,0,7,0,null],[8,7,0,1,2,null]],\"groups\":[],\"config\":{},\"version\":0.4}"
  },
  {
    "path": "editor/examples/benchmark.json",
    "content": "{\"last_node_id\":60,\"last_link_id\":50,\"nodes\":[{\"id\":9,\"type\":\"features/slots\",\"pos\":[846,473],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":6}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":8,\"type\":\"features/slots\",\"pos\":[671,475],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":10}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":18,\"type\":\"features/slots\",\"pos\":[751.5619834710739,854.9586776859505],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":16}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":[17,18]},{\"name\":\"B\",\"type\":\"number\",\"links\":[]}],\"properties\":{}},{\"id\":28,\"type\":\"features/slots\",\"pos\":[1386.361032999997,389.62936599999995],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":23}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":[24,25]},{\"name\":\"B\",\"type\":\"number\",\"links\":[]}],\"properties\":{}},{\"id\":35,\"type\":\"features/shape\",\"pos\":[1555.6387189504107,741.7260602148749],\"size\":{\"0\":140,\"1\":39},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"\",\"type\":\"number\",\"link\":26}],\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":38,\"type\":\"features/slots\",\"pos\":[1461.838718950411,987.9260602148759],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":28}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":[29,30]},{\"name\":\"B\",\"type\":\"number\",\"links\":[]}],\"properties\":{}},{\"id\":39,\"type\":\"features/slots\",\"pos\":[1376.8387189504106,1088.9260602148756],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":29}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":40,\"type\":\"features/slots\",\"pos\":[1551.838718950411,1086.9260602148754],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":30}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":45,\"type\":\"features/slots\",\"pos\":[588.5619834710739,1055.9586776859496],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":33}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":46,\"type\":\"features/slots\",\"pos\":[721.5619834710739,1058.9586776859496],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":34}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":19,\"type\":\"features/slots\",\"pos\":[666.5619834710739,955.9586776859505],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":17}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":[33]},{\"name\":\"B\",\"type\":\"number\",\"links\":[34]}],\"properties\":{}},{\"id\":47,\"type\":\"features/slots\",\"pos\":[846.5619834710739,1060.9586776859496],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":35}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":48,\"type\":\"features/slots\",\"pos\":[976.5619834710739,1059.9586776859496],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":36}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":20,\"type\":\"features/slots\",\"pos\":[841.5619834710739,953.9586776859505],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":18}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":[35]},{\"name\":\"B\",\"type\":\"number\",\"links\":[36]}],\"properties\":{}},{\"id\":29,\"type\":\"features/slots\",\"pos\":[1307,490],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":24}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":[37]},{\"name\":\"B\",\"type\":\"number\",\"links\":[38]}],\"properties\":{}},{\"id\":30,\"type\":\"features/slots\",\"pos\":[1476.3610329999974,488.62936599999995],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":25}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":[39]},{\"name\":\"B\",\"type\":\"number\",\"links\":[40]}],\"properties\":{}},{\"id\":7,\"type\":\"features/slots\",\"pos\":[756,374],\"size\":[100,40],\"flags\":{\"horizontal\":true,\"collapsed\":false},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":13}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":[10]},{\"name\":\"B\",\"type\":\"number\",\"links\":[6]}],\"properties\":{}},{\"id\":34,\"type\":\"features/widgets\",\"pos\":[1054,349],\"size\":{\"0\":189,\"1\":176},\"flags\":{\"collapsed\":false},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[23]}],\"properties\":{}},{\"id\":12,\"type\":\"input/gamepad\",\"pos\":[607,204],\"size\":{\"0\":155,\"1\":69},\"flags\":{\"collapsed\":false},\"mode\":0,\"outputs\":[{\"name\":\"left_x_axis\",\"type\":\"number\",\"links\":null},{\"name\":\"left_y_axis\",\"type\":\"number\",\"links\":null},{\"name\":\"button_pressed\",\"type\":-1,\"links\":[12]}],\"properties\":{\"gamepad_index\":0,\"threshold\":0.1}},{\"id\":4,\"type\":\"math/operation\",\"pos\":[604,106],\"size\":[164,51],\"flags\":{\"collapsed\":false},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":2},{\"name\":\"B\",\"type\":\"number\",\"link\":9}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[1]}],\"properties\":{\"A\":2,\"B\":0.5,\"OP\":\"+\"},\"shape\":2},{\"id\":2,\"type\":\"features/shape\",\"pos\":[867,112],\"size\":{\"0\":140,\"1\":39},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"\",\"type\":\"number\",\"link\":1}],\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":13,\"type\":\"events/log\",\"pos\":[868,215],\"size\":{\"0\":143,\"1\":30},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"event\",\"type\":-1,\"link\":12}],\"properties\":{}},{\"id\":14,\"type\":\"features/widgets\",\"pos\":[432,357],\"size\":{\"0\":209,\"1\":178},\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[13]}],\"properties\":{}},{\"id\":24,\"type\":\"features/widgets\",\"pos\":[429,841],\"size\":{\"0\":184.9173583984375,\"1\":176.34710693359375},\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[16]}],\"properties\":{}},{\"id\":44,\"type\":\"features/widgets\",\"pos\":[1154,942],\"size\":{\"0\":191,\"1\":174},\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[28]}],\"properties\":{}},{\"id\":36,\"type\":\"math/operation\",\"pos\":[1333,729],\"size\":[144,45],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":27},{\"name\":\"B\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[26]}],\"properties\":{\"A\":2,\"B\":1,\"OP\":\"+\"},\"shape\":2},{\"id\":42,\"type\":\"input/gamepad\",\"pos\":[1316,821],\"size\":{\"0\":182.3612823486328,\"1\":69.27394104003906},\"flags\":{\"collapsed\":false},\"mode\":0,\"outputs\":[{\"name\":\"left_x_axis\",\"type\":\"number\",\"links\":null},{\"name\":\"left_y_axis\",\"type\":\"number\",\"links\":null},{\"name\":\"button_pressed\",\"type\":-1,\"links\":[]}],\"properties\":{\"gamepad_index\":0,\"threshold\":0.1}},{\"id\":43,\"type\":\"events/log\",\"pos\":[1562,845],\"size\":{\"0\":142.16128540039062,\"1\":31.07394027709961},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"event\",\"type\":-1,\"link\":null}],\"properties\":{}},{\"id\":41,\"type\":\"widget/knob\",\"pos\":[1155,824],\"size\":[54,74],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[]}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.5,\"color\":\"#7AF\",\"precision\":2,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":37,\"type\":\"math/operation\",\"pos\":[1138,739],\"size\":[148,44],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":null},{\"name\":\"B\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[27]}],\"properties\":{\"A\":1,\"B\":1,\"OP\":\"+\"},\"color\":\"#233\",\"bgcolor\":\"#355\",\"shape\":2},{\"id\":33,\"type\":\"events/log\",\"pos\":[1493.3610329999974,224.629366],\"size\":{\"0\":147.6389617919922,\"1\":29.370634078979492},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"event\",\"type\":-1,\"link\":32}],\"properties\":{}},{\"id\":26,\"type\":\"math/operation\",\"pos\":[1268,128],\"size\":[144,50],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":22},{\"name\":\"B\",\"type\":\"number\",\"link\":31}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[21]}],\"properties\":{\"A\":2,\"B\":0.5,\"OP\":\"+\"},\"shape\":2},{\"id\":5,\"type\":\"math/operation\",\"pos\":[437,114],\"size\":[140,48],\"flags\":{\"collapsed\":true},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":null},{\"name\":\"B\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[2]}],\"properties\":{\"A\":1,\"B\":1,\"OP\":\"+\"},\"shape\":2},{\"id\":27,\"type\":\"math/operation\",\"pos\":[1061,129],\"size\":[144,44],\"flags\":{\"collapsed\":true},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":null},{\"name\":\"B\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[22]}],\"properties\":{\"A\":1,\"B\":1,\"OP\":\"+\"},\"shape\":2},{\"id\":49,\"type\":\"features/slots\",\"pos\":[1210,589],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":37}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":50,\"type\":\"features/slots\",\"pos\":[1342,591],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":38}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":51,\"type\":\"features/slots\",\"pos\":[1471,590],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":39}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":52,\"type\":\"features/slots\",\"pos\":[1597,588],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":40}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":25,\"type\":\"features/shape\",\"pos\":[1500,116],\"size\":{\"0\":140,\"1\":39},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"\",\"type\":\"number\",\"link\":21}],\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":10,\"type\":\"widget/knob\",\"pos\":[435,161],\"size\":[91,111],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[9]}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.5,\"color\":\"#7AF\",\"precision\":2,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":31,\"type\":\"widget/knob\",\"pos\":[1058,182],\"size\":[95,114],\"flags\":{\"collapsed\":false},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[31]}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.5,\"color\":\"#7AF\",\"precision\":2,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":32,\"type\":\"input/gamepad\",\"pos\":[1232,231],\"size\":{\"0\":191.6389617919922,\"1\":73.37063598632812},\"flags\":{\"collapsed\":false},\"mode\":0,\"outputs\":[{\"name\":\"left_x_axis\",\"type\":\"number\",\"links\":[46]},{\"name\":\"left_y_axis\",\"type\":\"number\",\"links\":[47]},{\"name\":\"button_pressed\",\"type\":-1,\"links\":[32]}],\"properties\":{\"gamepad_index\":0,\"threshold\":0.1}},{\"id\":57,\"type\":\"graphics/plot\",\"pos\":[1722,335],\"size\":{\"0\":140,\"1\":86},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"Number\",\"link\":48},{\"name\":\"B\",\"type\":\"Number\",\"link\":null},{\"name\":\"C\",\"type\":\"Number\",\"link\":null},{\"name\":\"D\",\"type\":\"Number\",\"link\":null}],\"properties\":{\"scale\":2}},{\"id\":21,\"type\":\"widget/knob\",\"pos\":[435,591],\"size\":[72,90],\"flags\":{\"collapsed\":false},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[43]}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.7297408895318863,\"color\":\"#7AF\",\"precision\":2,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(186,186,186,1.0)\"},{\"id\":16,\"type\":\"math/operation\",\"pos\":[583,611],\"size\":[140,47],\"flags\":{\"collapsed\":false},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":43},{\"name\":\"B\",\"type\":\"number\",\"link\":45}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[44]}],\"properties\":{\"A\":0.7297408895318863,\"B\":5000,\"OP\":\"*\"},\"shape\":2},{\"id\":54,\"type\":\"events/timer\",\"pos\":[764,620],\"size\":{\"0\":147,\"1\":30},\"flags\":{\"collapsed\":false},\"mode\":0,\"inputs\":[{\"name\":\"interval\",\"type\":\"number\",\"link\":44}],\"outputs\":[{\"name\":\"on_tick\",\"type\":-1,\"links\":[42]}],\"properties\":{\"interval\":1000,\"event\":\"tick\"},\"boxcolor\":\"#222\",\"shape\":2},{\"id\":23,\"type\":\"events/log\",\"pos\":[961,622],\"size\":{\"0\":137,\"1\":28},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"event\",\"type\":-1,\"link\":42}],\"properties\":{}},{\"id\":56,\"type\":\"graph/subgraph\",\"pos\":[1543,336],\"size\":{\"0\":140,\"1\":86},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"enabled\",\"type\":\"boolean\",\"link\":49},{\"name\":\"AxisX\",\"type\":0,\"link\":46},{\"name\":\"AxisY\",\"type\":0,\"link\":47}],\"outputs\":[{\"name\":\"sum\",\"type\":0,\"links\":[48]}],\"properties\":{\"enabled\":true},\"subgraph\":{\"last_node_id\":6,\"last_link_id\":4,\"nodes\":[{\"id\":4,\"type\":\"graph/output\",\"pos\":[1655,311],\"size\":[180,60],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"\",\"type\":0,\"link\":3}],\"properties\":{\"name\":\"sum\",\"type\":0}},{\"id\":2,\"type\":\"graph/input\",\"pos\":[1227,233],\"size\":[180,60],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"\",\"links\":[1]}],\"properties\":{\"name\":\"AxisX\",\"type\":\"\"}},{\"id\":3,\"type\":\"graph/input\",\"pos\":[1234,341],\"size\":[180,60],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"\",\"links\":[2]}],\"properties\":{\"name\":\"AxisY\",\"type\":\"\"}},{\"id\":6,\"type\":\"math/operation\",\"pos\":[1496,268],\"size\":[100,60],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":1},{\"name\":\"B\",\"type\":\"number\",\"link\":2}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[3]}],\"properties\":{\"A\":1,\"B\":1,\"OP\":\"+\"}}],\"links\":[[1,2,0,6,0,\"number\"],[2,3,0,6,1,\"number\"],[3,6,0,4,0,0]],\"groups\":[],\"config\":{},\"version\":0.4}},{\"id\":58,\"type\":\"widget/toggle\",\"pos\":[1686,131],\"size\":[160,44],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"\",\"type\":\"boolean\",\"link\":null},{\"name\":\"e\",\"type\":-1,\"link\":null}],\"outputs\":[{\"name\":\"v\",\"type\":\"boolean\",\"links\":[49]},{\"name\":\"e\",\"type\":-1,\"links\":null}],\"properties\":{\"font\":\"\",\"value\":true}},{\"id\":60,\"type\":\"widget/number\",\"pos\":[626,706],\"size\":[74,54],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":null}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":55,\"type\":\"basic/const\",\"pos\":[422,738],\"size\":{\"0\":139,\"1\":28},\"flags\":{\"collapsed\":false},\"mode\":0,\"outputs\":[{\"name\":\"value\",\"type\":\"number\",\"links\":[45],\"label\":\"5000.000\"}],\"properties\":{\"value\":5000}}],\"links\":[[1,4,0,2,0,\"number\"],[2,5,0,4,0,\"number\"],[6,7,1,9,0,\"number\"],[9,10,0,4,1,\"number\"],[10,7,0,8,0,\"number\"],[12,12,2,13,0,-1],[13,14,0,7,0,\"number\"],[16,24,0,18,0,\"number\"],[17,18,0,19,0,\"number\"],[18,18,0,20,0,\"number\"],[21,26,0,25,0,\"number\"],[22,27,0,26,0,\"number\"],[23,34,0,28,0,\"number\"],[24,28,0,29,0,\"number\"],[25,28,0,30,0,\"number\"],[26,36,0,35,0,\"number\"],[27,37,0,36,0,\"number\"],[28,44,0,38,0,\"number\"],[29,38,0,39,0,\"number\"],[30,38,0,40,0,\"number\"],[31,31,0,26,1,\"number\"],[32,32,2,33,0,-1],[33,19,0,45,0,\"number\"],[34,19,1,46,0,\"number\"],[35,20,0,47,0,\"number\"],[36,20,1,48,0,\"number\"],[37,29,0,49,0,\"number\"],[38,29,1,50,0,\"number\"],[39,30,0,51,0,\"number\"],[40,30,1,52,0,\"number\"],[42,54,0,23,0,-1],[43,21,0,16,0,\"number\"],[44,16,0,54,0,\"number\"],[45,55,0,16,1,\"number\"],[46,32,0,56,1,0],[47,32,1,56,2,0],[48,56,0,57,0,\"Number\"],[49,58,0,56,0,\"boolean\"]],\"groups\":[{\"title\":\"Group\",\"bounding\":[417,292,564,255],\"color\":\"#3f789e\"},{\"title\":\"Group\",\"bounding\":[1120,678,642,461],\"color\":\"#A88\"},{\"title\":\"Group\",\"bounding\":[411,777,679,361],\"color\":\"#8A8\"}],\"config\":{},\"version\":0.4}"
  },
  {
    "path": "editor/examples/copypaste.json",
    "content": "{\"last_node_id\":62,\"last_link_id\":157,\"nodes\":[{\"id\":35,\"type\":\"widget/number\",\"pos\":[354.0977802999988,-703.6940983999988],\"size\":[80,60],\"flags\":{},\"order\":0,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[32,52,72,94,115,138]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":36,\"type\":\"widget/number\",\"pos\":[356.0977802999989,-596.6940983999991],\"size\":[80,60],\"flags\":{},\"order\":1,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[33,53,73,95,116,139]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":37,\"type\":\"widget/number\",\"pos\":[359.0977802999989,-495.69409839999975],\"size\":[80,60],\"flags\":{},\"order\":2,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[34,54,74,96,117,140]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":38,\"type\":\"widget/number\",\"pos\":[361.0977802999988,-385.69409839999867],\"size\":[80,60],\"flags\":{},\"order\":3,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[35,55,75,97,118,141]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":39,\"type\":\"widget/number\",\"pos\":[363.0977802999988,-269.6940983999987],\"size\":[80,60],\"flags\":{},\"order\":4,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[36,56,76,98,119,142]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":40,\"type\":\"widget/number\",\"pos\":[366.3589802999999,-157.40999840000026],\"size\":[80,60],\"flags\":{},\"order\":5,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[37,57,77,99,120,143]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":41,\"type\":\"widget/number\",\"pos\":[367.3589802999999,-49.40999839999942],\"size\":[80,60],\"flags\":{},\"order\":6,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[38,58,78,100,121,144]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":42,\"type\":\"widget/number\",\"pos\":[366.3589802999999,53.59000160000091],\"size\":[80,60],\"flags\":{},\"order\":7,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[39,59,79,101,122,145]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":43,\"type\":\"widget/number\",\"pos\":[366.3589802999999,157.59000160000005],\"size\":[80,60],\"flags\":{},\"order\":8,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[40,60,80,102,123,146]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":45,\"type\":\"widget/number\",\"pos\":[374,367.58986919999944],\"size\":[80,60],\"flags\":{},\"order\":9,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[42,62,82,104,125,148]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":46,\"type\":\"widget/number\",\"pos\":[375,464.58986920000046],\"size\":[80,60],\"flags\":{},\"order\":10,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[43,63,83,105,126,149]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":47,\"type\":\"widget/number\",\"pos\":[377,564.5898692000013],\"size\":[80,60],\"flags\":{},\"order\":11,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[44,64,84,106,127,150]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":48,\"type\":\"widget/number\",\"pos\":[380,661.5898692000013],\"size\":[80,60],\"flags\":{},\"order\":12,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[45,65,85,107,128,151]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":44,\"type\":\"widget/number\",\"pos\":[372,264],\"size\":[80,60],\"flags\":{},\"order\":13,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[41,61,81,103,124,147]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":49,\"type\":\"widget/number\",\"pos\":[383,768],\"size\":[80,60],\"flags\":{},\"order\":14,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[46,66,86,108,129,152]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":50,\"type\":\"widget/number\",\"pos\":[386,871],\"size\":[80,60],\"flags\":{},\"order\":15,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[47,67,87,109,130,153]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":51,\"type\":\"widget/number\",\"pos\":[390,976],\"size\":[80,60],\"flags\":{},\"order\":16,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[48,68,88,110,131,154]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":52,\"type\":\"widget/number\",\"pos\":[396,1079],\"size\":[80,60],\"flags\":{},\"order\":17,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[49,89,111]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":55,\"type\":\"features/largeinput_editor\",\"pos\":[814.2834913867189,1257.9045020637213],\"size\":[200,410],\"flags\":{},\"order\":28,\"mode\":0,\"inputs\":[{\"name\":\"in 1\",\"type\":\"number\",\"link\":71,\"slot_index\":0},{\"name\":\"in 2\",\"type\":\"number\",\"link\":72,\"slot_index\":1},{\"name\":\"in 3\",\"type\":\"number\",\"link\":73,\"slot_index\":2},{\"name\":\"in 4\",\"type\":\"number\",\"link\":74,\"slot_index\":3},{\"name\":\"in 5\",\"type\":\"number\",\"link\":75,\"slot_index\":4},{\"name\":\"in 6\",\"type\":\"number\",\"link\":76,\"slot_index\":5},{\"name\":\"in 7\",\"type\":\"number\",\"link\":77,\"slot_index\":6},{\"name\":\"in 8\",\"type\":\"number\",\"link\":78,\"slot_index\":7},{\"name\":\"in 9\",\"type\":\"number\",\"link\":79,\"slot_index\":8},{\"name\":\"in 10\",\"type\":\"number\",\"link\":80,\"slot_index\":9},{\"name\":\"in 11\",\"type\":\"number\",\"link\":81,\"slot_index\":10},{\"name\":\"in 12\",\"type\":\"number\",\"link\":82,\"slot_index\":11},{\"name\":\"in 13\",\"type\":\"number\",\"link\":83,\"slot_index\":12},{\"name\":\"in 14\",\"type\":\"number\",\"link\":84,\"slot_index\":13},{\"name\":\"in 15\",\"type\":\"number\",\"link\":85,\"slot_index\":14},{\"name\":\"in 16\",\"type\":\"number\",\"link\":86,\"slot_index\":15},{\"name\":\"in 17\",\"type\":\"number\",\"link\":87,\"slot_index\":16},{\"name\":\"in 18\",\"type\":\"number\",\"link\":88,\"slot_index\":17},{\"name\":\"in 19\",\"type\":\"number\",\"link\":89,\"slot_index\":18},{\"name\":\"in 20\",\"type\":\"number\",\"link\":91,\"slot_index\":19}],\"properties\":{}},{\"id\":54,\"type\":\"features/largeinput_editor\",\"pos\":[816.2834913867189,800.9045020637204],\"size\":[200,410],\"flags\":{},\"order\":27,\"mode\":0,\"inputs\":[{\"name\":\"in 1\",\"type\":\"number\",\"link\":51,\"slot_index\":0},{\"name\":\"in 2\",\"type\":\"number\",\"link\":52,\"slot_index\":1},{\"name\":\"in 3\",\"type\":\"number\",\"link\":53,\"slot_index\":2},{\"name\":\"in 4\",\"type\":\"number\",\"link\":54,\"slot_index\":3},{\"name\":\"in 5\",\"type\":\"number\",\"link\":55,\"slot_index\":4},{\"name\":\"in 6\",\"type\":\"number\",\"link\":56,\"slot_index\":5},{\"name\":\"in 7\",\"type\":\"number\",\"link\":57,\"slot_index\":6},{\"name\":\"in 8\",\"type\":\"number\",\"link\":58,\"slot_index\":7},{\"name\":\"in 9\",\"type\":\"number\",\"link\":59,\"slot_index\":8},{\"name\":\"in 10\",\"type\":\"number\",\"link\":60,\"slot_index\":9},{\"name\":\"in 11\",\"type\":\"number\",\"link\":61,\"slot_index\":10},{\"name\":\"in 12\",\"type\":\"number\",\"link\":62,\"slot_index\":11},{\"name\":\"in 13\",\"type\":\"number\",\"link\":63,\"slot_index\":12},{\"name\":\"in 14\",\"type\":\"number\",\"link\":64,\"slot_index\":13},{\"name\":\"in 15\",\"type\":\"number\",\"link\":65,\"slot_index\":14},{\"name\":\"in 16\",\"type\":\"number\",\"link\":66,\"slot_index\":15},{\"name\":\"in 17\",\"type\":\"number\",\"link\":67,\"slot_index\":16},{\"name\":\"in 18\",\"type\":\"number\",\"link\":68,\"slot_index\":17},{\"name\":\"in 19\",\"type\":\"number\",\"link\":92,\"slot_index\":18},{\"name\":\"in 20\",\"type\":\"number\",\"link\":70,\"slot_index\":19}],\"properties\":{}},{\"id\":33,\"type\":\"features/largeinput_editor\",\"pos\":[818.2834913867189,334.90450206372003],\"size\":[200,410],\"flags\":{},\"order\":26,\"mode\":0,\"inputs\":[{\"name\":\"in 1\",\"type\":\"number\",\"link\":31,\"slot_index\":0},{\"name\":\"in 2\",\"type\":\"number\",\"link\":32,\"slot_index\":1},{\"name\":\"in 3\",\"type\":\"number\",\"link\":33,\"slot_index\":2},{\"name\":\"in 4\",\"type\":\"number\",\"link\":34,\"slot_index\":3},{\"name\":\"in 5\",\"type\":\"number\",\"link\":35,\"slot_index\":4},{\"name\":\"in 6\",\"type\":\"number\",\"link\":36,\"slot_index\":5},{\"name\":\"in 7\",\"type\":\"number\",\"link\":37,\"slot_index\":6},{\"name\":\"in 8\",\"type\":\"number\",\"link\":38,\"slot_index\":7},{\"name\":\"in 9\",\"type\":\"number\",\"link\":39,\"slot_index\":8},{\"name\":\"in 10\",\"type\":\"number\",\"link\":40,\"slot_index\":9},{\"name\":\"in 11\",\"type\":\"number\",\"link\":41,\"slot_index\":10},{\"name\":\"in 12\",\"type\":\"number\",\"link\":42,\"slot_index\":11},{\"name\":\"in 13\",\"type\":\"number\",\"link\":43,\"slot_index\":12},{\"name\":\"in 14\",\"type\":\"number\",\"link\":44,\"slot_index\":13},{\"name\":\"in 15\",\"type\":\"number\",\"link\":45,\"slot_index\":14},{\"name\":\"in 16\",\"type\":\"number\",\"link\":46,\"slot_index\":15},{\"name\":\"in 17\",\"type\":\"number\",\"link\":47,\"slot_index\":16},{\"name\":\"in 18\",\"type\":\"number\",\"link\":48,\"slot_index\":17},{\"name\":\"in 19\",\"type\":\"number\",\"link\":49,\"slot_index\":18},{\"name\":\"in 20\",\"type\":\"number\",\"link\":50,\"slot_index\":19}],\"properties\":{}},{\"id\":53,\"type\":\"widget/number\",\"pos\":[399,1182],\"size\":[80,60],\"flags\":{},\"order\":18,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[50,70,112,135],\"slot_index\":0}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":57,\"type\":\"widget/number\",\"pos\":[405,1443],\"size\":[80,60],\"flags\":{},\"order\":19,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[92,136,155],\"slot_index\":0}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":61,\"type\":\"features/largeinput_editor\",\"pos\":[1070.283491386718,1265.9045020637213],\"size\":[200,410],\"flags\":{},\"order\":25,\"mode\":0,\"inputs\":[{\"name\":\"in 1\",\"type\":\"number\",\"link\":137,\"slot_index\":0},{\"name\":\"in 2\",\"type\":\"number\",\"link\":138,\"slot_index\":1},{\"name\":\"in 3\",\"type\":\"number\",\"link\":139,\"slot_index\":2},{\"name\":\"in 4\",\"type\":\"number\",\"link\":140,\"slot_index\":3},{\"name\":\"in 5\",\"type\":\"number\",\"link\":141,\"slot_index\":4},{\"name\":\"in 6\",\"type\":\"number\",\"link\":142,\"slot_index\":5},{\"name\":\"in 7\",\"type\":\"number\",\"link\":143,\"slot_index\":6},{\"name\":\"in 8\",\"type\":\"number\",\"link\":144,\"slot_index\":7},{\"name\":\"in 9\",\"type\":\"number\",\"link\":145,\"slot_index\":8},{\"name\":\"in 10\",\"type\":\"number\",\"link\":146,\"slot_index\":9},{\"name\":\"in 11\",\"type\":\"number\",\"link\":147,\"slot_index\":10},{\"name\":\"in 12\",\"type\":\"number\",\"link\":148,\"slot_index\":11},{\"name\":\"in 13\",\"type\":\"number\",\"link\":149,\"slot_index\":12},{\"name\":\"in 14\",\"type\":\"number\",\"link\":150,\"slot_index\":13},{\"name\":\"in 15\",\"type\":\"number\",\"link\":151,\"slot_index\":14},{\"name\":\"in 16\",\"type\":\"number\",\"link\":152,\"slot_index\":15},{\"name\":\"in 17\",\"type\":\"number\",\"link\":153,\"slot_index\":16},{\"name\":\"in 18\",\"type\":\"number\",\"link\":154,\"slot_index\":17},{\"name\":\"in 19\",\"type\":\"number\",\"link\":155,\"slot_index\":18},{\"name\":\"in 20\",\"type\":\"number\",\"link\":157,\"slot_index\":19}],\"properties\":{}},{\"id\":56,\"type\":\"widget/number\",\"pos\":[446,1703],\"size\":[80,60],\"flags\":{},\"order\":20,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[91,157],\"slot_index\":0}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":60,\"type\":\"features/largeinput_editor\",\"pos\":[1069.283491386718,804.9045020637204],\"size\":[200,410],\"flags\":{},\"order\":24,\"mode\":0,\"inputs\":[{\"name\":\"in 1\",\"type\":\"number\",\"link\":114,\"slot_index\":0},{\"name\":\"in 2\",\"type\":\"number\",\"link\":115,\"slot_index\":1},{\"name\":\"in 3\",\"type\":\"number\",\"link\":116,\"slot_index\":2},{\"name\":\"in 4\",\"type\":\"number\",\"link\":117,\"slot_index\":3},{\"name\":\"in 5\",\"type\":\"number\",\"link\":118,\"slot_index\":4},{\"name\":\"in 6\",\"type\":\"number\",\"link\":119,\"slot_index\":5},{\"name\":\"in 7\",\"type\":\"number\",\"link\":120,\"slot_index\":6},{\"name\":\"in 8\",\"type\":\"number\",\"link\":121,\"slot_index\":7},{\"name\":\"in 9\",\"type\":\"number\",\"link\":122,\"slot_index\":8},{\"name\":\"in 10\",\"type\":\"number\",\"link\":123,\"slot_index\":9},{\"name\":\"in 11\",\"type\":\"number\",\"link\":124,\"slot_index\":10},{\"name\":\"in 12\",\"type\":\"number\",\"link\":125,\"slot_index\":11},{\"name\":\"in 13\",\"type\":\"number\",\"link\":126,\"slot_index\":12},{\"name\":\"in 14\",\"type\":\"number\",\"link\":127,\"slot_index\":13},{\"name\":\"in 15\",\"type\":\"number\",\"link\":128,\"slot_index\":14},{\"name\":\"in 16\",\"type\":\"number\",\"link\":129,\"slot_index\":15},{\"name\":\"in 17\",\"type\":\"number\",\"link\":130,\"slot_index\":16},{\"name\":\"in 18\",\"type\":\"number\",\"link\":131,\"slot_index\":17},{\"name\":\"in 19\",\"type\":\"number\",\"link\":136,\"slot_index\":18},{\"name\":\"in 20\",\"type\":\"number\",\"link\":135,\"slot_index\":19}],\"properties\":{}},{\"id\":59,\"type\":\"widget/number\",\"pos\":[942.2834913867189,139.9045020637209],\"size\":[80,60],\"flags\":{},\"order\":21,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[113,114,137]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":34,\"type\":\"widget/number\",\"pos\":[354,-815],\"size\":[80,60],\"flags\":{},\"order\":22,\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[31,51,71]}],\"properties\":{\"min\":-1000,\"max\":1000,\"value\":1,\"step\":1}},{\"id\":58,\"type\":\"features/largeinput_editor\",\"pos\":[1066,342],\"size\":[200,410],\"flags\":{},\"order\":23,\"mode\":0,\"inputs\":[{\"name\":\"in 1\",\"type\":\"number\",\"link\":113,\"slot_index\":0},{\"name\":\"in 2\",\"type\":\"number\",\"link\":94,\"slot_index\":1},{\"name\":\"in 3\",\"type\":\"number\",\"link\":95,\"slot_index\":2},{\"name\":\"in 4\",\"type\":\"number\",\"link\":96,\"slot_index\":3},{\"name\":\"in 5\",\"type\":\"number\",\"link\":97,\"slot_index\":4},{\"name\":\"in 6\",\"type\":\"number\",\"link\":98,\"slot_index\":5},{\"name\":\"in 7\",\"type\":\"number\",\"link\":99,\"slot_index\":6},{\"name\":\"in 8\",\"type\":\"number\",\"link\":100,\"slot_index\":7},{\"name\":\"in 9\",\"type\":\"number\",\"link\":101,\"slot_index\":8},{\"name\":\"in 10\",\"type\":\"number\",\"link\":102,\"slot_index\":9},{\"name\":\"in 11\",\"type\":\"number\",\"link\":103,\"slot_index\":10},{\"name\":\"in 12\",\"type\":\"number\",\"link\":104,\"slot_index\":11},{\"name\":\"in 13\",\"type\":\"number\",\"link\":105,\"slot_index\":12},{\"name\":\"in 14\",\"type\":\"number\",\"link\":106,\"slot_index\":13},{\"name\":\"in 15\",\"type\":\"number\",\"link\":107,\"slot_index\":14},{\"name\":\"in 16\",\"type\":\"number\",\"link\":108,\"slot_index\":15},{\"name\":\"in 17\",\"type\":\"number\",\"link\":109,\"slot_index\":16},{\"name\":\"in 18\",\"type\":\"number\",\"link\":110,\"slot_index\":17},{\"name\":\"in 19\",\"type\":\"number\",\"link\":111,\"slot_index\":18},{\"name\":\"in 20\",\"type\":\"number\",\"link\":112,\"slot_index\":19}],\"properties\":{}}],\"links\":[[31,34,0,33,0,\"number\"],[32,35,0,33,1,\"number\"],[33,36,0,33,2,\"number\"],[34,37,0,33,3,\"number\"],[35,38,0,33,4,\"number\"],[36,39,0,33,5,\"number\"],[37,40,0,33,6,\"number\"],[38,41,0,33,7,\"number\"],[39,42,0,33,8,\"number\"],[40,43,0,33,9,\"number\"],[41,44,0,33,10,\"number\"],[42,45,0,33,11,\"number\"],[43,46,0,33,12,\"number\"],[44,47,0,33,13,\"number\"],[45,48,0,33,14,\"number\"],[46,49,0,33,15,\"number\"],[47,50,0,33,16,\"number\"],[48,51,0,33,17,\"number\"],[49,52,0,33,18,\"number\"],[50,53,0,33,19,\"number\"],[51,34,0,54,0,\"number\"],[52,35,0,54,1,\"number\"],[53,36,0,54,2,\"number\"],[54,37,0,54,3,\"number\"],[55,38,0,54,4,\"number\"],[56,39,0,54,5,\"number\"],[57,40,0,54,6,\"number\"],[58,41,0,54,7,\"number\"],[59,42,0,54,8,\"number\"],[60,43,0,54,9,\"number\"],[61,44,0,54,10,\"number\"],[62,45,0,54,11,\"number\"],[63,46,0,54,12,\"number\"],[64,47,0,54,13,\"number\"],[65,48,0,54,14,\"number\"],[66,49,0,54,15,\"number\"],[67,50,0,54,16,\"number\"],[68,51,0,54,17,\"number\"],[70,53,0,54,19,\"number\"],[71,34,0,55,0,\"number\"],[72,35,0,55,1,\"number\"],[73,36,0,55,2,\"number\"],[74,37,0,55,3,\"number\"],[75,38,0,55,4,\"number\"],[76,39,0,55,5,\"number\"],[77,40,0,55,6,\"number\"],[78,41,0,55,7,\"number\"],[79,42,0,55,8,\"number\"],[80,43,0,55,9,\"number\"],[81,44,0,55,10,\"number\"],[82,45,0,55,11,\"number\"],[83,46,0,55,12,\"number\"],[84,47,0,55,13,\"number\"],[85,48,0,55,14,\"number\"],[86,49,0,55,15,\"number\"],[87,50,0,55,16,\"number\"],[88,51,0,55,17,\"number\"],[89,52,0,55,18,\"number\"],[91,56,0,55,19,\"number\"],[92,57,0,54,18,\"number\"],[94,35,0,58,1,\"number\"],[95,36,0,58,2,\"number\"],[96,37,0,58,3,\"number\"],[97,38,0,58,4,\"number\"],[98,39,0,58,5,\"number\"],[99,40,0,58,6,\"number\"],[100,41,0,58,7,\"number\"],[101,42,0,58,8,\"number\"],[102,43,0,58,9,\"number\"],[103,44,0,58,10,\"number\"],[104,45,0,58,11,\"number\"],[105,46,0,58,12,\"number\"],[106,47,0,58,13,\"number\"],[107,48,0,58,14,\"number\"],[108,49,0,58,15,\"number\"],[109,50,0,58,16,\"number\"],[110,51,0,58,17,\"number\"],[111,52,0,58,18,\"number\"],[112,53,0,58,19,\"number\"],[113,59,0,58,0,\"number\"],[114,59,0,60,0,\"number\"],[115,35,0,60,1,\"number\"],[116,36,0,60,2,\"number\"],[117,37,0,60,3,\"number\"],[118,38,0,60,4,\"number\"],[119,39,0,60,5,\"number\"],[120,40,0,60,6,\"number\"],[121,41,0,60,7,\"number\"],[122,42,0,60,8,\"number\"],[123,43,0,60,9,\"number\"],[124,44,0,60,10,\"number\"],[125,45,0,60,11,\"number\"],[126,46,0,60,12,\"number\"],[127,47,0,60,13,\"number\"],[128,48,0,60,14,\"number\"],[129,49,0,60,15,\"number\"],[130,50,0,60,16,\"number\"],[131,51,0,60,17,\"number\"],[135,53,0,60,19,\"number\"],[136,57,0,60,18,\"number\"],[137,59,0,61,0,\"number\"],[138,35,0,61,1,\"number\"],[139,36,0,61,2,\"number\"],[140,37,0,61,3,\"number\"],[141,38,0,61,4,\"number\"],[142,39,0,61,5,\"number\"],[143,40,0,61,6,\"number\"],[144,41,0,61,7,\"number\"],[145,42,0,61,8,\"number\"],[146,43,0,61,9,\"number\"],[147,44,0,61,10,\"number\"],[148,45,0,61,11,\"number\"],[149,46,0,61,12,\"number\"],[150,47,0,61,13,\"number\"],[151,48,0,61,14,\"number\"],[152,49,0,61,15,\"number\"],[153,50,0,61,16,\"number\"],[154,51,0,61,17,\"number\"],[155,57,0,61,18,\"number\"],[157,56,0,61,19,\"number\"]],\"groups\":[{\"title\":\"Use Ctrl+C/Ctrl+Shift+V to easily copy and paste new nodes maintaining connection to the outputs of unselected nodes\",\"bounding\":[1137,140,263,82],\"color\":\"#3f789e\"}],\"config\":{},\"extra\":{},\"version\":0.4}"
  },
  {
    "path": "editor/examples/features.json",
    "content": "{\"last_node_id\":14,\"last_link_id\":14,\"nodes\":[{\"id\":9,\"type\":\"features/slots\",\"pos\":[847,479],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":6}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":7,\"type\":\"features/slots\",\"pos\":[757,380],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":13}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":[10]},{\"name\":\"B\",\"type\":\"number\",\"links\":[6]}],\"properties\":{}},{\"id\":8,\"type\":\"features/slots\",\"pos\":[672,481],\"size\":[100,40],\"flags\":{\"horizontal\":true},\"mode\":0,\"inputs\":[{\"name\":\"C\",\"type\":\"number\",\"link\":10}],\"outputs\":[{\"name\":\"A\",\"type\":\"number\",\"links\":null},{\"name\":\"B\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":5,\"type\":\"math/operation\",\"pos\":[413,101],\"size\":[140,34],\"flags\":{\"collapsed\":true},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":null},{\"name\":\"B\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[2]}],\"properties\":{\"A\":1,\"B\":1,\"OP\":\"+\"},\"shape\":2},{\"id\":12,\"type\":\"input/gamepad\",\"pos\":[593,208],\"size\":{\"0\":175,\"1\":74},\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"left_x_axis\",\"type\":\"number\",\"links\":null},{\"name\":\"left_y_axis\",\"type\":\"number\",\"links\":null},{\"name\":\"button_pressed\",\"type\":-1,\"links\":[12]}],\"properties\":{\"gamepad_index\":0,\"threshold\":0.1}},{\"id\":13,\"type\":\"events/log\",\"pos\":[862,246],\"size\":{\"0\":144,\"1\":32},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"event\",\"type\":-1,\"link\":12}],\"properties\":{}},{\"id\":14,\"type\":\"features/widgets\",\"pos\":[441,365],\"size\":{\"0\":180,\"1\":170},\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[13]}],\"properties\":{}},{\"id\":10,\"type\":\"widget/knob\",\"pos\":[421,197],\"size\":[74,92],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[9]}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.5,\"wcolor\":\"#7AF\",\"size\":50}},{\"id\":4,\"type\":\"math/operation\",\"pos\":[596,116],\"size\":[148,48],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":2},{\"name\":\"B\",\"type\":\"number\",\"link\":9}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[1]}],\"properties\":{\"A\":1,\"B\":1,\"OP\":\"+\"},\"shape\":2},{\"id\":2,\"type\":\"features/shape\",\"pos\":[850,97],\"size\":{\"0\":140,\"1\":39},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"\",\"type\":\"number\",\"link\":1}],\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":null}],\"properties\":{}}],\"links\":[[1,4,0,2,0,\"number\"],[2,5,0,4,0,\"number\"],[6,7,1,9,0,\"number\"],[9,10,0,4,1,\"number\"],[10,7,0,8,0,\"number\"],[12,12,2,13,0,-1],[13,14,0,7,0,\"number\"]],\"groups\":[{\"title\":\"Group\",\"bounding\":[418,298,609,255],\"color\":\"#3f789e\"}],\"config\":{}}"
  },
  {
    "path": "editor/examples/midi_generation.json",
    "content": "{\"last_node_id\":47,\"last_link_id\":64,\"nodes\":[{\"id\":8,\"type\":\"midi/generator\",\"pos\":[548,390],\"size\":{\"0\":140,\"1\":66},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"generate\",\"type\":-1,\"link\":11},{\"name\":\"scale\",\"type\":\"string\",\"link\":47},{\"name\":\"octave\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"note\",\"type\":-1,\"links\":[10,19]}],\"properties\":{\"notes\":\"A,B,C\",\"octave\":2,\"duration\":0.5,\"mode\":\"sequence\"}},{\"id\":20,\"type\":\"midi/transpose\",\"pos\":[726,489],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":-1,\"link\":19},{\"name\":\"amount\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"out\",\"type\":-1,\"links\":[21]}],\"properties\":{\"amount\":5}},{\"id\":32,\"type\":\"midi/event\",\"pos\":[1465,656],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"send\",\"type\":-1,\"link\":null},{\"name\":\"assign\",\"type\":-1,\"link\":44}],\"outputs\":[{\"name\":\"on_midi\",\"type\":-1,\"links\":null},{\"name\":\"note\",\"type\":\"number\",\"links\":[45]}],\"properties\":{\"channel\":0,\"cmd\":128,\"value1\":0,\"value2\":0}},{\"id\":19,\"type\":\"midi/play\",\"pos\":[1132,611],\"size\":{\"0\":140,\"1\":66},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"note\",\"type\":-1,\"link\":53},{\"name\":\"volume\",\"type\":\"number\",\"link\":36},{\"name\":\"duration\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"note\",\"type\":-1,\"links\":[35,44]}],\"properties\":{\"volume\":0.3599999999999999,\"duration\":4,\"value\":0}},{\"id\":21,\"type\":\"midi/quantize\",\"pos\":[903,589],\"size\":{\"0\":159.60000610351562,\"1\":46},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"note\",\"type\":-1,\"link\":21},{\"name\":\"scale\",\"type\":\"string\",\"link\":49}],\"outputs\":[{\"name\":\"out\",\"type\":-1,\"links\":[53]}],\"properties\":{\"scale\":\"A,A#,B,C,C#,D,D#,E,F,F#,G,G#\",\"amount\":\"A,B,C,D,E,F,G\"}},{\"id\":37,\"type\":\"basic/watch\",\"pos\":[547,615],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"value\",\"type\":0,\"link\":52,\"label\":\"A,B,C,B\"}],\"properties\":{}},{\"id\":35,\"type\":\"basic/string\",\"pos\":[79,456],\"size\":[210,58],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"value\",\"type\":\"string\",\"links\":[50],\"label\":\"A,B,C\"}],\"title\":\"NOTE SCALE\",\"properties\":{\"value\":\"A,B,C,B\"}},{\"id\":7,\"type\":\"midi/generator\",\"pos\":[549,289],\"size\":{\"0\":140,\"1\":66},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"generate\",\"type\":-1,\"link\":5},{\"name\":\"scale\",\"type\":\"string\",\"link\":48},{\"name\":\"octave\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"note\",\"type\":-1,\"links\":[7,12]}],\"properties\":{\"notes\":\"A,B,C\",\"octave\":2,\"duration\":0.5,\"mode\":\"random\"}},{\"id\":41,\"type\":\"midi/generator\",\"pos\":[552,189],\"size\":{\"0\":140,\"1\":66},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"generate\",\"type\":-1,\"link\":57},{\"name\":\"scale\",\"type\":\"string\",\"link\":55},{\"name\":\"octave\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"note\",\"type\":-1,\"links\":[62]}],\"properties\":{\"notes\":\"A,B,C\",\"octave\":3,\"duration\":0.5,\"mode\":\"sequence\"}},{\"id\":12,\"type\":\"events/timer\",\"pos\":[180,284],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"interval\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"on_tick\",\"type\":-1,\"links\":[11]}],\"properties\":{\"interval\":1200,\"event\":\"tick\"},\"boxcolor\":\"#222\"},{\"id\":34,\"type\":\"logic/selector\",\"pos\":[351,468],\"size\":{\"0\":140,\"1\":106},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"sel\",\"type\":\"number\",\"link\":58},{\"name\":\"A\",\"type\":0,\"link\":46},{\"name\":\"B\",\"type\":0,\"link\":50},{\"name\":\"C\",\"type\":0,\"link\":59},{\"name\":\"D\",\"type\":0,\"link\":null}],\"outputs\":[{\"name\":\"out\",\"links\":[47,48,49,52,55]}],\"properties\":{}},{\"id\":47,\"type\":\"midi/keys\",\"pos\":[1153,88],\"size\":[423,104],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"note\",\"type\":-1,\"link\":62},{\"name\":\"reset\",\"type\":-1,\"link\":null}],\"outputs\":[{\"name\":\"note\",\"type\":-1,\"links\":[63]}],\"properties\":{\"num_octaves\":2,\"start_octave\":3}},{\"id\":15,\"type\":\"math/floor\",\"pos\":[505,85],\"size\":[140,26],\"flags\":{\"collapsed\":true},\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":\"number\",\"link\":14}],\"outputs\":[{\"name\":\"out\",\"type\":\"number\",\"links\":[15]}],\"properties\":{}},{\"id\":14,\"type\":\"math/rand\",\"pos\":[344,83],\"size\":[140,26],\"flags\":{\"collapsed\":true},\"mode\":0,\"outputs\":[{\"name\":\"value\",\"type\":\"number\",\"links\":[14],\"label\":\"1.191\"}],\"properties\":{\"min\":-1,\"max\":2}},{\"id\":16,\"type\":\"math/operation\",\"pos\":[645,85],\"size\":[100,50],\"flags\":{\"collapsed\":true},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"number\",\"link\":15},{\"name\":\"B\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"=\",\"type\":\"number\",\"links\":[16]}],\"properties\":{\"A\":1,\"B\":12,\"OP\":\"*\"}},{\"id\":10,\"type\":\"basic/string\",\"pos\":[77,360],\"size\":[208,48],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"value\",\"type\":\"string\",\"links\":[46],\"label\":\"A,B,C\"}],\"title\":\"NOTE SCALE\",\"properties\":{\"value\":\"A,B,C,D,E,F,G\"}},{\"id\":43,\"type\":\"basic/string\",\"pos\":[79,556],\"size\":[210,58],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"value\",\"type\":\"string\",\"links\":[59],\"label\":\"A,B,C\"}],\"title\":\"NOTE SCALE\",\"properties\":{\"value\":\"D,E,F,G,F,E\"}},{\"id\":44,\"type\":\"math/rand\",\"pos\":[143,664],\"size\":[140,26],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"value\",\"type\":\"number\",\"links\":[58],\"label\":\"0.750\"}],\"properties\":{\"min\":0,\"max\":1}},{\"id\":11,\"type\":\"midi/play\",\"pos\":[1135,496],\"size\":{\"0\":140,\"1\":66},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"note\",\"type\":-1,\"link\":10},{\"name\":\"volume\",\"type\":\"number\",\"link\":18},{\"name\":\"duration\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"note\",\"type\":-1,\"links\":[34,43]}],\"properties\":{\"volume\":0.3599999999999999,\"duration\":4,\"value\":0}},{\"id\":13,\"type\":\"midi/transpose\",\"pos\":[893,258],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":-1,\"link\":12},{\"name\":\"amount\",\"type\":\"number\",\"link\":16}],\"outputs\":[{\"name\":\"out\",\"type\":-1,\"links\":[54]}],\"properties\":{\"amount\":12}},{\"id\":4,\"type\":\"midi/play\",\"pos\":[1155,249],\"size\":{\"0\":140,\"1\":66},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"note\",\"type\":-1,\"link\":54},{\"name\":\"volume\",\"type\":\"number\",\"link\":17},{\"name\":\"duration\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"note\",\"type\":-1,\"links\":[33,39]}],\"properties\":{\"volume\":0.21000000000000005,\"duration\":1,\"value\":0}},{\"id\":30,\"type\":\"midi/event\",\"pos\":[1433,260],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"send\",\"type\":-1,\"link\":null},{\"name\":\"assign\",\"type\":-1,\"link\":39}],\"outputs\":[{\"name\":\"on_midi\",\"type\":-1,\"links\":null},{\"name\":\"note\",\"type\":\"number\",\"links\":[38]}],\"properties\":{\"channel\":0,\"cmd\":128,\"value1\":57,\"value2\":0}},{\"id\":28,\"type\":\"midi/output\",\"pos\":[1428,414],\"size\":{\"0\":140,\"1\":66},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"send\",\"type\":-1,\"link\":33},{\"name\":\"send\",\"type\":-1,\"link\":34},{\"name\":\"send\",\"type\":-1,\"link\":35}],\"properties\":{\"port\":0}},{\"id\":31,\"type\":\"midi/event\",\"pos\":[1469,563],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"send\",\"type\":-1,\"link\":null},{\"name\":\"assign\",\"type\":-1,\"link\":43}],\"outputs\":[{\"name\":\"on_midi\",\"type\":-1,\"links\":null},{\"name\":\"note\",\"type\":\"number\",\"links\":[42]}],\"properties\":{\"channel\":0,\"cmd\":128,\"value1\":50,\"value2\":0}},{\"id\":29,\"type\":\"graphics/plot\",\"pos\":[1675,328],\"size\":{\"0\":348,\"1\":139},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"A\",\"type\":\"Number\",\"link\":38},{\"name\":\"B\",\"type\":\"Number\",\"link\":42},{\"name\":\"C\",\"type\":\"Number\",\"link\":45},{\"name\":\"D\",\"type\":\"Number\",\"link\":null}],\"properties\":{\"scale\":100}},{\"id\":46,\"type\":\"math/rand\",\"pos\":[1455,42],\"size\":[140,26],\"flags\":{\"collapsed\":true},\"mode\":0,\"outputs\":[{\"name\":\"value\",\"type\":\"number\",\"links\":[60],\"label\":\"0.007\"}],\"properties\":{\"min\":0,\"max\":0.2}},{\"id\":39,\"type\":\"midi/play\",\"pos\":[1656,116],\"size\":{\"0\":140,\"1\":66},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"note\",\"type\":-1,\"link\":63},{\"name\":\"volume\",\"type\":\"number\",\"link\":60},{\"name\":\"duration\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"note\",\"type\":-1,\"links\":[]}],\"properties\":{\"volume\":0.006812153971126511,\"duration\":1,\"value\":0}},{\"id\":3,\"type\":\"events/timer\",\"pos\":[178,212],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"interval\",\"type\":\"number\",\"link\":null}],\"outputs\":[{\"name\":\"on_tick\",\"type\":-1,\"links\":[5,57]}],\"properties\":{\"interval\":300,\"event\":\"tick\"},\"boxcolor\":\"#222\"},{\"id\":18,\"type\":\"widget/knob\",\"pos\":[819,62],\"size\":[82.78512396694214,93.87603305785123],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[18,36]}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.4504132231404958,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":17,\"type\":\"widget/knob\",\"pos\":[916,62],\"size\":[78.34710743801656,94.70247933884298],\"flags\":{\"collapsed\":false},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"number\",\"links\":[17]}],\"properties\":{\"min\":0,\"max\":1,\"value\":0.21000000000000005,\"wcolor\":\"#7AF\",\"size\":50},\"boxcolor\":\"rgba(128,128,128,1.0)\"},{\"id\":6,\"type\":\"midi/show\",\"pos\":[898,357],\"size\":[266.5950413223138,61.685950413223],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"on_midi\",\"type\":-1,\"link\":7}],\"properties\":{}}],\"links\":[[5,3,0,7,0,-1],[7,7,0,6,0,-1],[10,8,0,11,0,-1],[11,12,0,8,0,-1],[12,7,0,13,0,-1],[14,14,0,15,0,\"number\"],[15,15,0,16,0,\"number\"],[16,16,0,13,1,\"number\"],[17,17,0,4,1,\"number\"],[18,18,0,11,1,\"number\"],[19,8,0,20,0,-1],[21,20,0,21,0,-1],[33,4,0,28,0,-1],[34,11,0,28,1,-1],[35,19,0,28,2,-1],[36,18,0,19,1,\"number\"],[38,30,1,29,0,\"Number\"],[39,4,0,30,1,-1],[42,31,1,29,1,\"Number\"],[43,11,0,31,1,-1],[44,19,0,32,1,-1],[45,32,1,29,2,\"Number\"],[46,10,0,34,1,0],[47,34,0,8,1,\"string\"],[48,34,0,7,1,\"string\"],[49,34,0,21,1,\"string\"],[50,35,0,34,2,0],[52,34,0,37,0,0],[53,21,0,19,0,-1],[54,13,0,4,0,-1],[55,34,0,41,1,\"string\"],[57,3,0,41,0,-1],[58,44,0,34,0,\"number\"],[59,43,0,34,3,0],[60,46,0,39,1,\"number\"],[62,41,0,47,0,-1],[63,47,0,39,0,-1]],\"groups\":[],\"config\":{}}"
  },
  {
    "path": "editor/examples/subgraph.json",
    "content": "{\"last_node_id\":6,\"last_link_id\":5,\"nodes\":[{\"id\":3,\"type\":\"basic/time\",\"pos\":[312,145],\"size\":{\"0\":140,\"1\":46},\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"in ms\",\"type\":\"number\",\"links\":null},{\"name\":\"in sec\",\"type\":\"number\",\"links\":[1]}],\"properties\":{}},{\"id\":4,\"type\":\"basic/watch\",\"pos\":[864,156],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"value\",\"type\":0,\"link\":2,\"label\":\"5.000\"}],\"properties\":{}},{\"id\":6,\"type\":\"events/counter\",\"pos\":[864,229],\"size\":{\"0\":140,\"1\":66},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"inc\",\"type\":-1,\"link\":4},{\"name\":\"dec\",\"type\":-1,\"link\":null},{\"name\":\"reset\",\"type\":-1,\"link\":null}],\"outputs\":[{\"name\":\"change\",\"type\":-1,\"links\":null},{\"name\":\"num\",\"type\":\"number\",\"links\":null}],\"properties\":{}},{\"id\":2,\"type\":\"graph/subgraph\",\"pos\":[573,168],\"size\":{\"0\":140,\"1\":86},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"enabled\",\"type\":\"boolean\",\"link\":null},{\"name\":\"foo\",\"type\":\"\",\"link\":1},{\"name\":\"EV\",\"type\":-1,\"link\":3}],\"outputs\":[{\"name\":\"faa\",\"type\":0,\"links\":[2]},{\"name\":\"EV\",\"type\":-1,\"links\":[4]}],\"properties\":{\"enabled\":true},\"subgraph\":{\"last_node_id\":7,\"last_link_id\":6,\"nodes\":[{\"id\":3,\"type\":\"graph/output\",\"pos\":[1119,139],\"size\":[180,60],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"\",\"type\":0,\"link\":2}],\"properties\":{\"name\":\"faa\",\"type\":0}},{\"id\":4,\"type\":\"math/floor\",\"pos\":[872,194],\"size\":[112,28],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"in\",\"type\":\"number\",\"link\":1}],\"outputs\":[{\"name\":\"out\",\"type\":\"number\",\"links\":[2]}],\"properties\":{}},{\"id\":2,\"type\":\"graph/input\",\"pos\":[440,149],\"size\":[180,60],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":\"\",\"links\":[1]}],\"properties\":{\"name\":\"foo\",\"type\":\"\"}},{\"id\":5,\"type\":\"graph/input\",\"pos\":[460,282],\"size\":[180,60],\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"\",\"type\":-1,\"links\":[4]}],\"properties\":{\"name\":\"EV\",\"type\":-1}},{\"id\":6,\"type\":\"graph/output\",\"pos\":[1054,293],\"size\":[180,60],\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"\",\"type\":-1,\"link\":5}],\"properties\":{\"name\":\"EV\",\"type\":-1}},{\"id\":7,\"type\":\"events/delay\",\"pos\":[742,300],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"inputs\":[{\"name\":\"event\",\"type\":-1,\"link\":4}],\"outputs\":[{\"name\":\"on_time\",\"type\":-1,\"links\":[5]}],\"properties\":{\"time_in_ms\":1000}}],\"links\":[[1,2,0,4,0,\"number\"],[2,4,0,3,0,0],[4,5,0,7,0,-1],[5,7,0,6,0,-1]],\"groups\":[],\"config\":{},\"version\":0.4}},{\"id\":5,\"type\":\"events/timer\",\"pos\":[311,240],\"size\":{\"0\":140,\"1\":26},\"flags\":{},\"mode\":0,\"outputs\":[{\"name\":\"on_tick\",\"type\":-1,\"links\":[3]}],\"properties\":{\"interval\":2000,\"event\":\"tick\"},\"boxcolor\":\"#222\"}],\"links\":[[1,3,1,2,1,0],[2,2,0,4,0,0],[3,5,0,2,2,-1],[4,2,1,6,0,-1]],\"groups\":[],\"config\":{},\"version\":0.4}"
  },
  {
    "path": "editor/index.html",
    "content": "<!-- Javi Agenjo (@tamat) on 31/9/2011 -->\n<!DOCTYPE html>\n<html><head>\n    <title>LiteGraph</title>\n\t<!--<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">-->\n\t<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"../css/litegraph.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"../css/litegraph-editor.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">\n\t</head>\n<body>\n\t<div id=\"main\">\n\t</div>\n\n    <script type=\"text/javascript\" src=\"../external/jquery-1.6.2.min.js\"></script>\n    <script type=\"text/javascript\" src=\"https://tamats.com/projects/sillyserver/src/sillyclient.js\"></script>\n\t<!-- <script type=\"text/javascript\" src=\"https://unpkg.com/codeflask/build/codeflask.min.js\"></script> -->\n\t<script type=\"text/javascript\" src=\"js/libs/gl-matrix-min.js\"></script>\n    <script type=\"text/javascript\" src=\"js/libs/audiosynth.js\"></script>\n    <script type=\"text/javascript\" src=\"js/libs/midi-parser.js\"></script>\n\n    <script type=\"text/javascript\" src=\"../src/litegraph.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/litegraph-editor.js\"></script>\n    <script type=\"text/javascript\" src=\"js/defaults.js\"></script>\n\n    <script type=\"text/javascript\" src=\"../src/nodes/base.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/logic.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/events.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/math.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/math3d.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/strings.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/interface.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/geometry.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/graphics.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/input.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/midi.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/audio.js\"></script>\n    <script type=\"text/javascript\" src=\"../src/nodes/network.js\"></script>\n\n    <script type=\"text/javascript\" src=\"js/demos.js\"></script>\n\t<script type=\"text/javascript\" src=\"js/code.js\"></script>\n\n\t<script type=\"text/javascript\" src=\"../src/nodes/others.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "editor/js/code.js",
    "content": "var webgl_canvas = null;\n\nLiteGraph.node_images_path = \"../nodes_data/\";\n\nvar editor = new LiteGraph.Editor(\"main\",{miniwindow:false});\nwindow.graphcanvas = editor.graphcanvas;\nwindow.graph = editor.graph;\nupdateEditorHiPPICanvas();\nwindow.addEventListener(\"resize\", function() { \n  editor.graphcanvas.resize();\n  updateEditorHiPPICanvas();\n} );\n//window.addEventListener(\"keydown\", editor.graphcanvas.processKey.bind(editor.graphcanvas) );\nwindow.onbeforeunload = function(){\n\tvar data = JSON.stringify( graph.serialize() );\n\tlocalStorage.setItem(\"litegraphg demo backup\", data );\n}\n\nfunction updateEditorHiPPICanvas() {\n  const ratio = window.devicePixelRatio;\n  if(ratio == 1) { return }\n  const rect = editor.canvas.parentNode.getBoundingClientRect();\n  const { width, height } = rect;\n  editor.canvas.width = width * ratio;\n  editor.canvas.height = height * ratio;\n  editor.canvas.style.width = width + \"px\";\n  editor.canvas.style.height = height + \"px\";\n  editor.canvas.getContext(\"2d\").scale(ratio, ratio);\n  return editor.canvas;\n}\n\n//enable scripting\nLiteGraph.allow_scripts = true;\n\n//test\n//editor.graphcanvas.viewport = [200,200,400,400];\n\n//create scene selector\nvar elem = document.createElement(\"span\");\nelem.id = \"LGEditorTopBarSelector\";\nelem.className = \"selector\";\nelem.innerHTML = \"\";\nelem.innerHTML += \"Demo <select><option>Empty</option></select> <button class='btn' id='save'>Save</button><button class='btn' id='load'>Load</button><button class='btn' id='download'>Download</button> | <button class='btn' id='webgl'>WebGL</button> <button class='btn' id='multiview'>Multiview</button>\";\neditor.tools.appendChild(elem);\nvar select = elem.querySelector(\"select\");\nselect.addEventListener(\"change\", function(e){\n\tvar option = this.options[this.selectedIndex];\n\tvar url = option.dataset[\"url\"];\n\t\n\tif(url)\n\t\tgraph.load( url );\n\telse if(option.callback)\n\t\toption.callback();\n\telse\n\t\tgraph.clear();\n});\n\nelem.querySelector(\"#save\").addEventListener(\"click\",function(){\n\tconsole.log(\"saved\");\n\tlocalStorage.setItem( \"graphdemo_save\", JSON.stringify( graph.serialize() ) );\n});\n\nelem.querySelector(\"#load\").addEventListener(\"click\",function(){\n\tvar data = localStorage.getItem( \"graphdemo_save\" );\n\tif(data)\n\t\tgraph.configure( JSON.parse( data ) );\n\tconsole.log(\"loaded\");\n});\n\nelem.querySelector(\"#download\").addEventListener(\"click\",function(){\n\tvar data = JSON.stringify( graph.serialize() );\n\tvar file = new Blob( [ data ] );\n\tvar url = URL.createObjectURL( file );\n\tvar element = document.createElement(\"a\");\n\telement.setAttribute('href', url);\n\telement.setAttribute('download', \"graph.JSON\" );\n\telement.style.display = 'none';\n\tdocument.body.appendChild(element);\n\telement.click();\n\tdocument.body.removeChild(element);\n\tsetTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url\t\n});\n\nelem.querySelector(\"#webgl\").addEventListener(\"click\", enableWebGL );\nelem.querySelector(\"#multiview\").addEventListener(\"click\", function(){ editor.addMultiview()  } );\n\n\nfunction addDemo( name, url )\n{\n\tvar option = document.createElement(\"option\");\n\tif(url.constructor === String)\n\t\toption.dataset[\"url\"] = url;\n\telse\n\t\toption.callback = url;\n\toption.innerHTML = name;\n\tselect.appendChild( option );\n}\n\n//some examples\naddDemo(\"Features\", \"examples/features.json\");\naddDemo(\"Benchmark\", \"examples/benchmark.json\");\naddDemo(\"Subgraph\", \"examples/subgraph.json\");\naddDemo(\"Audio\", \"examples/audio.json\");\naddDemo(\"Audio Delay\", \"examples/audio_delay.json\");\naddDemo(\"Audio Reverb\", \"examples/audio_reverb.json\");\naddDemo(\"MIDI Generation\", \"examples/midi_generation.json\");\naddDemo(\"Copy Paste\", \"examples/copypaste.json\");\naddDemo(\"autobackup\", function(){\n\tvar data = localStorage.getItem(\"litegraphg demo backup\");\n\tif(!data)\n\t\treturn;\n\tvar graph_data = JSON.parse(data);\n\tgraph.configure( graph_data );\n});\n\n//allows to use the WebGL nodes like textures\nfunction enableWebGL()\n{\n\tif( webgl_canvas )\n\t{\n\t\twebgl_canvas.style.display = (webgl_canvas.style.display == \"none\" ? \"block\" : \"none\");\n\t\treturn;\n\t}\n\n\tvar libs = [\n\t\t\"js/libs/gl-matrix-min.js\",\n\t\t\"js/libs/litegl.js\",\n\t\t\"../src/nodes/gltextures.js\",\n\t\t\"../src/nodes/glfx.js\",\n\t\t\"../src/nodes/glshaders.js\",\n\t\t\"../src/nodes/geometry.js\"\n\t];\n\n\tfunction fetchJS()\n\t{\n\t\tif(libs.length == 0)\n\t\t\treturn on_ready();\n\n\t\tvar script = null;\n\t\tscript = document.createElement(\"script\");\n\t\tscript.onload = fetchJS;\n\t\tscript.src = libs.shift();\n\t\tdocument.head.appendChild(script);\n\t}\n\n\tfetchJS();\n\n\tfunction on_ready()\n\t{\n\t\tconsole.log(this.src);\n\t\tif(!window.GL)\n\t\t\treturn;\n\t\twebgl_canvas = document.createElement(\"canvas\");\n\t\twebgl_canvas.width = 400;\n\t\twebgl_canvas.height = 300;\n\t\twebgl_canvas.style.position = \"absolute\";\n\t\twebgl_canvas.style.top = \"0px\";\n\t\twebgl_canvas.style.right = \"0px\";\n\t\twebgl_canvas.style.border = \"1px solid #AAA\";\n\n\t\twebgl_canvas.addEventListener(\"click\", function(){\n\t\t\tvar rect = webgl_canvas.parentNode.getBoundingClientRect();\n\t\t\tif( webgl_canvas.width != rect.width )\n\t\t\t{\n\t\t\t\twebgl_canvas.width = rect.width;\n\t\t\t\twebgl_canvas.height = rect.height;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\twebgl_canvas.width = 400;\n\t\t\t\twebgl_canvas.height = 300;\n\t\t\t}\n\t\t});\n\n\t\tvar parent = document.querySelector(\".editor-area\");\n\t\tparent.appendChild( webgl_canvas );\n\t\tvar gl = GL.create({ canvas: webgl_canvas });\n\t\tif(!gl)\n\t\t\treturn;\n\n\t\teditor.graph.onBeforeStep = ondraw;\n\n\t\tconsole.log(\"webgl ready\");\n\t\tfunction ondraw ()\n\t\t{\n\t\t\tgl.clearColor(0,0,0,0);\n\t\t\tgl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );\n\t\t\tgl.viewport(0,0,gl.canvas.width, gl.canvas.height );\n\t\t}\n\t}\n}\n\n// Tests\n// CopyPasteWithConnectionToUnselectedOutputTest();\n// demo();"
  },
  {
    "path": "editor/js/defaults.js",
    "content": "\nLiteGraph.debug = false;\nLiteGraph.catch_exceptions = true;\nLiteGraph.throw_errors = true;\nLiteGraph.allow_scripts = false; //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration); which could lead to exploits\n\nLiteGraph.searchbox_extras = {}; //used to add extra features to the search box\nLiteGraph.auto_sort_node_types = true; // [true!] If set to true; will automatically sort node types / categories in the context menus\nLiteGraph.node_box_coloured_when_on = true; // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action); visual feedback\nLiteGraph.node_box_coloured_by_mode = true; // [true!] nodebox based on node mode; visual feedback\nLiteGraph.dialog_close_on_mouse_leave = true; // [false on mobile] better true if not touch device;\nLiteGraph.dialog_close_on_mouse_leave_delay = 500;\nLiteGraph.shift_click_do_break_link_from = false; // [false!] prefer false if results too easy to break links\nLiteGraph.click_do_break_link_to = false; // [false!]prefer false; way too easy to break links\nLiteGraph.search_hide_on_mouse_leave = true; // [false on mobile] better true if not touch device;\nLiteGraph.search_filter_enabled = true; // [true!] enable filtering slots type in the search widget; !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out]\nLiteGraph.search_show_all_on_open = true; // [true!] opens the results list when opening the search widget\n\nLiteGraph.auto_load_slot_types = true; // [if want false; use true; run; get vars values to be statically set; than disable] nodes types and nodeclass association with node types need to be calculated; if dont want this; calculate once and set registered_slot_[in/out]_types and slot_types_[in/out]\n/*// set these values if not using auto_load_slot_types\nLiteGraph.registered_slot_in_types = {}; // slot types for nodeclass\nLiteGraph.registered_slot_out_types = {}; // slot types for nodeclass\nLiteGraph.slot_types_in = []; // slot types IN\nLiteGraph.slot_types_out = []; // slot types OUT*/\n\nLiteGraph.alt_drag_do_clone_nodes = true; // [true!] very handy; ALT click to clone and drag the new node\nLiteGraph.do_add_triggers_slots = true; // [true!] will create and connect event slots when using action/events connections; !WILL CHANGE node mode when using onTrigger (enable mode colors); onExecuted does not need this\nLiteGraph.allow_multi_output_for_events = false; // [false!] being events; it is strongly reccomended to use them sequentially; one by one\nLiteGraph.middle_click_slot_add_default_node = true;  //[true!] allows to create and connect a ndoe clicking with the third button (wheel)\nLiteGraph.release_link_on_empty_shows_menu = true; //[true!] dragging a link to empty space will open a menu, add from list, search or defaults\nLiteGraph.pointerevents_method = \"mouse\"; // \"mouse\"|\"pointer\" use mouse for retrocompatibility issues? (none found @ now)\nLiteGraph.ctrl_shift_v_paste_connect_unselected_outputs = true; //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes"
  },
  {
    "path": "editor/js/defaults_mobile.js",
    "content": "\nLiteGraph.debug = false;\nLiteGraph.catch_exceptions = true;\nLiteGraph.throw_errors = true;\nLiteGraph.allow_scripts = false; //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration); which could lead to exploits\n\nLiteGraph.searchbox_extras = {}; //used to add extra features to the search box\nLiteGraph.auto_sort_node_types = true; // [true!] If set to true; will automatically sort node types / categories in the context menus\nLiteGraph.node_box_coloured_when_on = true; // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action); visual feedback\nLiteGraph.node_box_coloured_by_mode = true; // [true!] nodebox based on node mode; visual feedback\nLiteGraph.dialog_close_on_mouse_leave = false; // [false on mobile] better true if not touch device;\nLiteGraph.dialog_close_on_mouse_leave_delay = 500;\nLiteGraph.shift_click_do_break_link_from = false; // [false!] prefer false if results too easy to break links\nLiteGraph.click_do_break_link_to = false; // [false!]prefer false; way too easy to break links\nLiteGraph.search_hide_on_mouse_leave = false; // [false on mobile] better true if not touch device;\nLiteGraph.search_filter_enabled = true; // [true!] enable filtering slots type in the search widget; !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out]\nLiteGraph.search_show_all_on_open = true; // [true!] opens the results list when opening the search widget\n\nLiteGraph.auto_load_slot_types = true; // [if want false; use true; run; get vars values to be statically set; than disable] nodes types and nodeclass association with node types need to be calculated; if dont want this; calculate once and set registered_slot_[in/out]_types and slot_types_[in/out]\n/*// set these values if not using auto_load_slot_types\nLiteGraph.registered_slot_in_types = {}; // slot types for nodeclass\nLiteGraph.registered_slot_out_types = {}; // slot types for nodeclass\nLiteGraph.slot_types_in = []; // slot types IN\nLiteGraph.slot_types_out = []; // slot types OUT*/\n\nLiteGraph.alt_drag_do_clone_nodes = true; // [true!] very handy; ALT click to clone and drag the new node\nLiteGraph.do_add_triggers_slots = true; // [true!] will create and connect event slots when using action/events connections; !WILL CHANGE node mode when using onTrigger (enable mode colors); onExecuted does not need this\nLiteGraph.allow_multi_output_for_events = false; // [false!] being events; it is strongly reccomended to use them sequentially; one by one\nLiteGraph.middle_click_slot_add_default_node = true;  //[true!] allows to create and connect a ndoe clicking with the third button (wheel)\nLiteGraph.release_link_on_empty_shows_menu = true; //[true!] dragging a link to empty space will open a menu, add from list, search or defaults\nLiteGraph.pointerevents_method = \"pointer\"; // \"mouse\"|\"pointer\" use mouse for retrocompatibility issues? (none found @ now)\nLiteGraph.ctrl_shift_v_paste_connect_unselected_outputs = true; //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes"
  },
  {
    "path": "editor/js/demos.js",
    "content": "\nfunction demo()\n{\n\tmultiConnection();\n}\n\nfunction multiConnection()\n{\n\tvar node_button = LiteGraph.createNode(\"widget/button\");\n\tnode_button.pos = [100,400];\n\tgraph.add(node_button);\n\n\tvar node_console = LiteGraph.createNode(\"basic/console\");\n\tnode_console.pos = [400,400];\n\tgraph.add(node_console);\n\tnode_button.connect(0, node_console );\n\n\tvar node_const_A = LiteGraph.createNode(\"basic/const\");\n\tnode_const_A.pos = [200,200];\n\tgraph.add(node_const_A);\n\tnode_const_A.setValue(4.5);\n\n\tvar node_const_B = LiteGraph.createNode(\"basic/const\");\n\tnode_const_B.pos = [200,300];\n\tgraph.add(node_const_B);\n\tnode_const_B.setValue(10);\n\n\tvar node_math = LiteGraph.createNode(\"math/operation\");\n\tnode_math.pos = [400,200];\n\tgraph.add(node_math);\n\n\tvar node_watch = LiteGraph.createNode(\"basic/watch\");\n\tnode_watch.pos = [700,200];\n\tgraph.add(node_watch);\n\n\tvar node_watch2 = LiteGraph.createNode(\"basic/watch\");\n\tnode_watch2.pos = [700,300];\n\tgraph.add(node_watch2);\n\n\tnode_const_A.connect(0,node_math,0 );\n\tnode_const_B.connect(0,node_math,1 );\n\tnode_math.connect(0,node_watch,0 );\n\tnode_math.connect(0,node_watch2,0 );\n}\n\nfunction CopyPasteWithConnectionToUnselectedOutputTest()\n{\n\t// number\n\tvar nodeConstA = LiteGraph.createNode(\"basic/const\");\n\tnodeConstA.pos = [200,200];\n\tgraph.add(nodeConstA);\n\tnodeConstA.setValue(4.5);\n\n\t// number\n\tvar nodeConstB = LiteGraph.createNode(\"basic/const\");\n\tnodeConstB.pos = [200,300];\n\tgraph.add(nodeConstB);\n\tnodeConstB.setValue(10);\n\n\t// math\n\tvar nodeMath = LiteGraph.createNode(\"math/operation\");\n\tnodeMath.pos = [400,200];\n\tgraph.add(nodeMath);\n\n\t// connection\n\tnodeConstA.connect(0,nodeMath,0 );\n\tnodeConstB.connect(0,nodeMath,1 );\n\n\t// copy with unselected nodes connected\n\tgraphcanvas.selectNodes([nodeMath]);\n\tgraphcanvas.copyToClipboard();\n\tgraphcanvas.pasteFromClipboard(true);\n\n\tvar count = 1;\n\tvar lastNode = null;\n\tfor (const [key, element] of Object.entries(graphcanvas.selected_nodes)) {\n\t\telement.pos = [nodeMath.pos[0], nodeMath.pos[1] + 100 * count];\n\t\tlastNode = element\n\t\tcount++;\n\t}\n\n\t// copy with unselected nodes unconnected\n\tgraphcanvas.pasteFromClipboard(false);\n\tvar count = 1;\n\tfor (const [key, element] of Object.entries(graphcanvas.selected_nodes)) {\n\t\telement.pos = [nodeMath.pos[0], lastNode.pos[1] + 100 * count];\n\t\tcount++;\n\t}\n}\n\nfunction sortTest()\n{\n\tvar rand = LiteGraph.createNode(\"math/rand\",null, {pos: [10,100] });\n\tgraph.add(rand);\n\n\tvar nodes = [];\n\tfor(var i = 4; i >= 1; i--)\n\t{\n\t\tvar n = LiteGraph.createNode(\"basic/watch\",null, {pos: [i * 120,100] });\n\t\tgraph.add(n);\n\t\tnodes[i-1] = n;\n\t}\n\n\trand.connect(0, nodes[0], 0);\n\n\tfor(var i = 0; i < nodes.length - 1; i++)\n\t\tnodes[i].connect(0,nodes[i+1], 0);\n}\n\nfunction benchmark()\n{\n\tvar num_nodes = 200;\n\tvar nodes = [];\n\tfor(var i = 0; i < num_nodes; i++)\n\t{\n\t\tvar n = LiteGraph.createNode(\"basic/watch\",null, {pos: [(2000 * Math.random())|0, (2000 * Math.random())|0] });\n\t\tgraph.add(n);\n\t\tnodes.push(n);\n\t}\n\n\tfor(var i = 0; i < nodes.length; i++)\n\t\tnodes[ (Math.random() * nodes.length)|0 ].connect(0, nodes[ (Math.random() * nodes.length)|0 ], 0 );\n}\n\n\n\n//Show value inside the debug console\nfunction TestWidgetsNode()\n{\n\tthis.addOutput(\"\",\"number\");\n\tthis.properties = {};\n\tvar that = this;\n\tthis.slider = this.addWidget(\"slider\",\"Slider\", 0.5, function(v){}, { min: 0, max: 1} );\n\tthis.number = this.addWidget(\"number\",\"Number\", 0.5, function(v){}, { min: 0, max: 100} );\n\tthis.combo = this.addWidget(\"combo\",\"Combo\", \"red\", function(v){}, { values:[\"red\",\"green\",\"blue\"]} );\n\tthis.text = this.addWidget(\"text\",\"Text\", \"edit me\", function(v){}, {} );\n\tthis.text2 = this.addWidget(\"text\",\"Text\", \"multiline\", function(v){}, { multiline:true } );\n\tthis.toggle = this.addWidget(\"toggle\",\"Toggle\", true, function(v){}, { on: \"enabled\", off:\"disabled\"} );\n\tthis.button = this.addWidget(\"button\",\"Button\", null, function(v){}, {} );\n\tthis.toggle2 = this.addWidget(\"toggle\",\"Disabled\", true, function(v){}, { on: \"enabled\", off:\"disabled\"} );\n\tthis.toggle2.disabled = true;\n\tthis.size = this.computeSize();\n\tthis.serialize_widgets = true;\n}\n\nTestWidgetsNode.title = \"Widgets\";\n\nLiteGraph.registerNodeType(\"features/widgets\", TestWidgetsNode );\n\n//Show value inside the debug console\nfunction TestSpecialNode()\n{\n\tthis.addInput(\"\",\"number\");\n\tthis.addOutput(\"\",\"number\");\n\tthis.properties = {};\n\tvar that = this;\n\tthis.size = this.computeSize();\n\tthis.enabled = false;\n\tthis.visible = false;\n}\n\nTestSpecialNode.title = \"Custom Shapes\";\nTestSpecialNode.title_mode = LiteGraph.TRANSPARENT_TITLE;\nTestSpecialNode.slot_start_y = 20;\n\nTestSpecialNode.prototype.onDrawBackground = function(ctx)\n{\n\tif(this.flags.collapsed)\n\t\treturn;\n\n\tctx.fillStyle = \"#555\";\n\tctx.fillRect(0,0,this.size[0],20);\n\n\tif(this.enabled)\n\t{\n\t\tctx.fillStyle = \"#AFB\";\n\t\tctx.beginPath();\n\t\tctx.moveTo(this.size[0]-20,0);\n\t\tctx.lineTo(this.size[0]-25,20);\n\t\tctx.lineTo(this.size[0],20);\n\t\tctx.lineTo(this.size[0],0);\n\t\tctx.fill();\n\t}\n\n\tif(this.visible)\n\t{\n\t\tctx.fillStyle = \"#ABF\";\n\t\tctx.beginPath();\n\t\tctx.moveTo(this.size[0]-40,0);\n\t\tctx.lineTo(this.size[0]-45,20);\n\t\tctx.lineTo(this.size[0]-25,20);\n\t\tctx.lineTo(this.size[0]-20,0);\n\t\tctx.fill();\n\t}\n\n\tctx.strokeStyle = \"#333\";\n\tctx.beginPath();\n\tctx.moveTo(0,20);\n\tctx.lineTo(this.size[0]+1,20);\n\tctx.moveTo(this.size[0]-20,0);\n\tctx.lineTo(this.size[0]-25,20);\n\tctx.moveTo(this.size[0]-40,0);\n\tctx.lineTo(this.size[0]-45,20);\n\tctx.stroke();\n\n\tif( this.mouseOver )\n\t{\n\t\tctx.fillStyle = \"#AAA\";\n\t\tctx.fillText( \"Example of helper\", 0, this.size[1] + 14 );\n\t}\n}\n\nTestSpecialNode.prototype.onMouseDown = function(e, pos)\n{\n\tif(pos[1] > 20)\n\t\treturn;\n\n\tif( pos[0] > this.size[0] - 20)\n\t\tthis.enabled = !this.enabled;\n\telse if( pos[0] > this.size[0] - 40)\n\t\tthis.visible = !this.visible;\n}\n\nTestSpecialNode.prototype.onBounding = function(rect)\n{\n\tif(!this.flags.collapsed && this.mouseOver )\n\t\trect[3] = this.size[1] + 20;\n}\n\nLiteGraph.registerNodeType(\"features/shape\", TestSpecialNode );\n\n\n\n//Show value inside the debug console\nfunction TestSlotsNode()\n{\n\tthis.addInput(\"C\",\"number\");\n\tthis.addOutput(\"A\",\"number\");\n\tthis.addOutput(\"B\",\"number\");\n\tthis.horizontal = true;\n\tthis.size = [100,40];\n}\n\nTestSlotsNode.title = \"Flat Slots\";\n\n\nLiteGraph.registerNodeType(\"features/slots\", TestSlotsNode );\n\n\n//Show value inside the debug console\nfunction TestPropertyEditorsNode()\n{\n\tthis.properties = {\n\t\tname: \"foo\",\n\t\tage: 10,\n\t\talive: true,\n\t\tchildren: [\"John\",\"Emily\",\"Charles\"],\n\t\tskills: {\n\t\t\tspeed: 10,\n\t\t\tdexterity: 100\n\t\t}\n\t}\n\n\tvar that = this;\n\tthis.addWidget(\"button\",\"Log\",null,function(){\n\t\tconsole.log(that.properties);\n\t});\n}\n\nTestPropertyEditorsNode.title = \"Properties\";\n\n\nLiteGraph.registerNodeType(\"features/properties_editor\", TestPropertyEditorsNode );\n\n\n\n//Show value inside the debug console\nfunction LargeInputNode()\n{\n\tthis.addInput(\"in 1\",\"number\");\n\tthis.addInput(\"in 2\",\"number\");\n\tthis.addInput(\"in 3\",\"number\");\n\tthis.addInput(\"in 4\",\"number\");\n\tthis.addInput(\"in 5\",\"number\");\n\tthis.addInput(\"in 6\",\"number\");\n\tthis.addInput(\"in 7\",\"number\");\n\tthis.addInput(\"in 8\",\"number\");\n\tthis.addInput(\"in 9\",\"number\");\n\tthis.addInput(\"in 10\",\"number\");\n\tthis.addInput(\"in 11\",\"number\");\n\tthis.addInput(\"in 12\",\"number\");\n\tthis.addInput(\"in 13\",\"number\");\n\tthis.addInput(\"in 14\",\"number\");\n\tthis.addInput(\"in 15\",\"number\");\n\tthis.addInput(\"in 16\",\"number\");\n\tthis.addInput(\"in 17\",\"number\");\n\tthis.addInput(\"in 18\",\"number\");\n\tthis.addInput(\"in 19\",\"number\");\n\tthis.addInput(\"in 20\",\"number\");\n\tthis.size = [200,410];\n}\n\nLargeInputNode.title = \"Large Input Node\";\n\n\nLiteGraph.registerNodeType(\"features/largeinput_editor\", LargeInputNode);\n\n"
  },
  {
    "path": "editor/js/libs/audiosynth.js",
    "content": "var Synth, AudioSynth, AudioSynthInstrument;\n!function(){\n\n\tvar URL = window.URL || window.webkitURL;\n\tvar Blob = window.Blob;\n\n\tif(!URL || !Blob) {\n\t\tthrow new Error('This browser does not support AudioSynth');\n\t}\n\n\tvar _encapsulated = false;\n\tvar AudioSynthInstance = null;\n\tvar pack = function(c,arg){ return [new Uint8Array([arg, arg >> 8]), new Uint8Array([arg, arg >> 8, arg >> 16, arg >> 24])][c]; };\n\tvar setPrivateVar = function(n,v,w,e){Object.defineProperty(this,n,{value:v,writable:!!w,enumerable:!!e});};\n\tvar setPublicVar = function(n,v,w){setPrivateVar.call(this,n,v,w,true);};\n\tAudioSynthInstrument = function AudioSynthInstrument(){this.__init__.apply(this,arguments);};\n\tvar setPriv = setPrivateVar.bind(AudioSynthInstrument.prototype);\n\tvar setPub = setPublicVar.bind(AudioSynthInstrument.prototype);\n\tsetPriv('__init__', function(a,b,c) {\n\t\tif(!_encapsulated) { throw new Error('AudioSynthInstrument can only be instantiated from the createInstrument method of the AudioSynth object.'); }\n\t\tsetPrivateVar.call(this, '_parent', a);\n\t\tsetPublicVar.call(this, 'name', b);\n\t\tsetPrivateVar.call(this, '_soundID', c);\n\t});\n\tsetPub('play', function(note, octave, duration,volume) {\n\t\treturn this._parent.play(this._soundID, note, octave, duration, volume);\n\t});\n\tsetPub('generate', function(note, octave, duration) {\n\t\treturn this._parent.generate(this._soundID, note, octave, duration);\n\t});\n\tAudioSynth = function AudioSynth(){if(AudioSynthInstance instanceof AudioSynth){return AudioSynthInstance;}else{ this.__init__(); return this; }};\n\tsetPriv = setPrivateVar.bind(AudioSynth.prototype);\n\tsetPub = setPublicVar.bind(AudioSynth.prototype);\n\tsetPriv('_debug',false,true);\n\tsetPriv('_bitsPerSample',16);\n\tsetPriv('_channels',1);\n\tsetPriv('_sampleRate',44100,true);\n\tsetPub('setSampleRate', function(v) {\n\t\tthis._sampleRate = Math.max(Math.min(v|0,44100), 4000);\n\t\tthis._clearCache();\n\t\treturn this._sampleRate;\n\t});\n\tsetPub('getSampleRate', function() { return this._sampleRate; });\n\tsetPriv('_volume',32768,true);\n\tsetPub('setVolume', function(v) {\n\t\tv = parseFloat(v); if(isNaN(v)) { v = 0; }\n\t\tv = Math.round(v*32768);\n\t\tthis._volume = Math.max(Math.min(v|0,32768), 0);\n\t\tthis._clearCache();\n\t\treturn this._volume;\n\t});\n\tsetPub('getVolume', function() { return Math.round(this._volume/32768*10000)/10000; });\n\tsetPriv('_notes',{'C':261.63,'C#':277.18,'D':293.66,'D#':311.13,'E':329.63,'F':346.23,'F#':369.99,'G':392.00,'G#':415.30,'A':440.00,'A#':466.16,'B':493.88});\n\tsetPriv('_fileCache',[],true);\n\tsetPriv('_temp',{},true);\n\tsetPriv('_sounds',[],true);\n\tsetPriv('_mod',[function(i,s,f,x){return Math.sin((2 * Math.PI)*(i/s)*f+x);}]);\n\tsetPriv('_resizeCache', function() {\n\t\tvar f = this._fileCache;\n\t\tvar l = this._sounds.length;\n\t\twhile(f.length<l) {\n\t\t\tvar octaveList = [];\n\t\t\tfor(var i = 0; i < 8; i++) {\n\t\t\t\tvar noteList = {};\n\t\t\t\tfor(var k in this._notes) {\n\t\t\t\t\tnoteList[k] = {};\n\t\t\t\t} \n\t\t\t\toctaveList.push(noteList);\n\t\t\t}\n\t\t\tf.push(octaveList);\n\t\t}\n\t});\n\tsetPriv('_clearCache', function() {\n\t\tthis._fileCache = [];\n\t\tthis._resizeCache();\n\t});\n\tsetPub('generate', function(sound, note, octave, duration) {\n\t\tvar thisSound = this._sounds[sound];\n\t\tif(!thisSound) {\n\t\t\tfor(var i=0;i<this._sounds.length;i++) {\n\t\t\t\tif(this._sounds[i].name==sound) {\n\t\t\t\t\tthisSound = this._sounds[i];\n\t\t\t\t\tsound = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif(!thisSound) { throw new Error('Invalid sound or sound ID: ' + sound); }\n\t\tvar t = (new Date).valueOf();\n\t\tthis._temp = {};\n\t\toctave |= 0;\n\t\toctave = Math.min(8, Math.max(1, octave));\n\t\tvar time = !duration?2:parseFloat(duration);\n\t\tif(typeof(this._notes[note])=='undefined') { throw new Error(note + ' is not a valid note.'); }\n\t\tif(typeof(this._fileCache[sound][octave-1][note][time])!='undefined') {\n\t\t\tif(this._debug) { console.log((new Date).valueOf() - t, 'ms to retrieve (cached)'); }\n\t\t\treturn this._fileCache[sound][octave-1][note][time];\n\t\t} else {\n\t\t\tvar frequency = this._notes[note] * Math.pow(2,octave-4);\n\t\t\tvar sampleRate = this._sampleRate;\n\t\t\tvar volume = this._volume;\n\t\t\tvar channels = this._channels;\n\t\t\tvar bitsPerSample = this._bitsPerSample;\n\t\t\tvar attack = thisSound.attack(sampleRate, frequency, volume);\n\t\t\tvar dampen = thisSound.dampen(sampleRate, frequency, volume);\n\t\t\tvar waveFunc = thisSound.wave;\n\t\t\tvar waveBind = {modulate: this._mod, vars: this._temp};\n\t\t\tvar val = 0;\n\t\t\tvar curVol = 0;\n\n\t\t\tvar data = new Uint8Array(new ArrayBuffer(Math.ceil(sampleRate * time * 2)));\n\t\t\tvar attackLen = (sampleRate * attack) | 0;\n\t\t\tvar decayLen = (sampleRate * time) | 0;\n\n\t\t\tfor (var i = 0 | 0; i !== attackLen; i++) {\n\t\t\n\t\t\t\tval = volume * (i/(sampleRate*attack)) * waveFunc.call(waveBind, i, sampleRate, frequency, volume);\n\n\t\t\t\tdata[i << 1] = val;\n\t\t\t\tdata[(i << 1) + 1] = val >> 8;\n\n\t\t\t}\n\n\t\t\tfor (; i !== decayLen; i++) {\n\n\t\t\t\tval = volume * Math.pow((1-((i-(sampleRate*attack))/(sampleRate*(time-attack)))),dampen) * waveFunc.call(waveBind, i, sampleRate, frequency, volume);\n\n\t\t\t\tdata[i << 1] = val;\n\t\t\t\tdata[(i << 1) + 1] = val >> 8;\n\n\t\t\t}\n\n\t\t\tvar out = [\n\t\t\t\t'RIFF',\n\t\t\t\tpack(1, 4 + (8 + 24/* chunk 1 length */) + (8 + 8/* chunk 2 length */)), // Length\n\t\t\t\t'WAVE',\n\t\t\t\t// chunk 1\n\t\t\t\t'fmt ', // Sub-chunk identifier\n\t\t\t\tpack(1, 16), // Chunk length\n\t\t\t\tpack(0, 1), // Audio format (1 is linear quantization)\n\t\t\t\tpack(0, channels),\n\t\t\t\tpack(1, sampleRate),\n\t\t\t\tpack(1, sampleRate * channels * bitsPerSample / 8), // Byte rate\n\t\t\t\tpack(0, channels * bitsPerSample / 8),\n\t\t\t\tpack(0, bitsPerSample),\n\t\t\t\t// chunk 2\n\t\t\t\t'data', // Sub-chunk identifier\n\t\t\t\tpack(1, data.length * channels * bitsPerSample / 8), // Chunk length\n\t\t\t\tdata\n\t\t\t];\n\t\t\tvar blob = new Blob(out, {type: 'audio/wav'});\n\t\t\tvar dataURI = URL.createObjectURL(blob);\n\t\t\tthis._fileCache[sound][octave-1][note][time] = dataURI;\n\t\t\tif(this._debug) { console.log((new Date).valueOf() - t, 'ms to generate'); }\n\t\t\treturn dataURI;\n\t\t}\n\t});\n\tsetPub('play', function(sound, note, octave, duration, volume) {\n\t\tvar src = this.generate( sound, note, octave, duration );\n\t\tvar audio = new Audio(src);\n\t\tif(volume != null)\n\t\t{\n\t\t\tif(volume <= 0)\n\t\t\t\treturn true;\n\t\t\taudio.volume = volume > 1 ? 1 : volume;\n\t\t}\n\t\taudio.play();\n\t\treturn true;\n\t});\n\tsetPub('debug', function() { this._debug = true; });\n\tsetPub('createInstrument', function(sound) {\n\t\tvar n = 0;\n\t\tvar found = false;\n\t\tif(typeof(sound)=='string') {\n\t\t\tfor(var i=0;i<this._sounds.length;i++) {\n\t\t\t\tif(this._sounds[i].name==sound) {\n\t\t\t\t\tfound = true;\n\t\t\t\t\tn = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif(this._sounds[sound]) {\n\t\t\t\tn = sound;\n\t\t\t\tsound = this._sounds[n].name;\n\t\t\t\tfound = true;\n\t\t\t}\n\t\t}\n\t\tif(!found) { throw new Error('Invalid sound or sound ID: ' + sound); }\n\t\t_encapsulated = true;\n\t\tvar ins = new AudioSynthInstrument(this, sound, n);\n\t\t_encapsulated = false;\n\t\treturn ins;\n\t});\n\tsetPub('listSounds', function() {\n\t\tvar r = [];\n\t\tfor(var i=0;i<this._sounds.length;i++) {\n\t\t\tr.push(this._sounds[i].name);\n\t\t}\n\t\treturn r;\n\t});\n\tsetPriv('__init__', function(){\n\t\tthis._resizeCache();\n\t});\n\tsetPub('loadSoundProfile', function() {\n\t\tfor(var i=0,len=arguments.length;i<len;i++) {\n\t\t\to = arguments[i];\n\t\t\tif(!(o instanceof Object)) { throw new Error('Invalid sound profile.'); }\n\t\t\tthis._sounds.push(o);\n\t\t}\n\t\tthis._resizeCache();\n\t\treturn true;\n\t});\n\tsetPub('loadModulationFunction', function() {\n\t\tfor(var i=0,len=arguments.length;i<len;i++) {\n\t\t\tf = arguments[i];\n\t\t\tif(typeof(f)!='function') { throw new Error('Invalid modulation function.'); }\n\t\t\tthis._mod.push(f);\n\t\t}\n\t\treturn true;\n\t});\n\tAudioSynthInstance = new AudioSynth();\n\tSynth = AudioSynthInstance;\n}();\n\nSynth.loadModulationFunction(\n\tfunction(i, sampleRate, frequency, x) { return 1 * Math.sin(2 * Math.PI * ((i / sampleRate) * frequency) + x); },\n\tfunction(i, sampleRate, frequency, x) { return 1 * Math.sin(4 * Math.PI * ((i / sampleRate) * frequency) + x); },\n\tfunction(i, sampleRate, frequency, x) { return 1 * Math.sin(8 * Math.PI * ((i / sampleRate) * frequency) + x); },\n\tfunction(i, sampleRate, frequency, x) { return 1 * Math.sin(0.5 * Math.PI * ((i / sampleRate) * frequency) + x); },\n\tfunction(i, sampleRate, frequency, x) { return 1 * Math.sin(0.25 * Math.PI * ((i / sampleRate) * frequency) + x); },\n\tfunction(i, sampleRate, frequency, x) { return 0.5 * Math.sin(2 * Math.PI * ((i / sampleRate) * frequency) + x); },\n\tfunction(i, sampleRate, frequency, x) { return 0.5 * Math.sin(4 * Math.PI * ((i / sampleRate) * frequency) + x); },\n\tfunction(i, sampleRate, frequency, x) { return 0.5 * Math.sin(8 * Math.PI * ((i / sampleRate) * frequency) + x); },\n\tfunction(i, sampleRate, frequency, x) { return 0.5 * Math.sin(0.5 * Math.PI * ((i / sampleRate) * frequency) + x); },\n\tfunction(i, sampleRate, frequency, x) { return 0.5 * Math.sin(0.25 * Math.PI * ((i / sampleRate) * frequency) + x); }\n);\n\nSynth.loadSoundProfile({\n\tname: 'piano',\n\tattack: function() { return 0.002; },\n\tdampen: function(sampleRate, frequency, volume) {\n\t\treturn Math.pow(0.5*Math.log((frequency*volume)/sampleRate),2);\n\t},\n\twave: function(i, sampleRate, frequency, volume) {\n\t\tvar base = this.modulate[0];\n\t\treturn this.modulate[1](\n\t\t\ti,\n\t\t\tsampleRate,\n\t\t\tfrequency,\n\t\t\tMath.pow(base(i, sampleRate, frequency, 0), 2) +\n\t\t\t\t(0.75 * base(i, sampleRate, frequency, 0.25)) +\n\t\t\t\t(0.1 * base(i, sampleRate, frequency, 0.5))\n\t\t);\n\t}\n},\n{\n\tname: 'organ',\n\tattack: function() { return 0.3 },\n\tdampen: function(sampleRate, frequency) { return 1+(frequency * 0.01); },\n\twave: function(i, sampleRate, frequency) {\n\t\tvar base = this.modulate[0];\n\t\treturn this.modulate[1](\n\t\t\ti,\n\t\t\tsampleRate,\n\t\t\tfrequency,\n\t\t\tbase(i, sampleRate, frequency, 0) +\n\t\t\t\t0.5*base(i, sampleRate, frequency, 0.25) +\n\t\t\t\t0.25*base(i, sampleRate, frequency, 0.5)\n\t\t);\n\t}\n},\n{\n\tname: 'acoustic',\n\tattack:\tfunction() { return 0.002; },\n\tdampen: function() { return 1; },\n\twave: function(i, sampleRate, frequency) {\n\n\t\tvar vars = this.vars;\n\t\tvars.valueTable = !vars.valueTable?[]:vars.valueTable;\n\t\tif(typeof(vars.playVal)=='undefined') { vars.playVal = 0; }\n\t\tif(typeof(vars.periodCount)=='undefined') { vars.periodCount = 0; }\n\t\n\t\tvar valueTable = vars.valueTable;\n\t\tvar playVal = vars.playVal;\n\t\tvar periodCount = vars.periodCount;\n\n\t\tvar period = sampleRate/frequency;\n\t\tvar p_hundredth = Math.floor((period-Math.floor(period))*100);\n\n\t\tvar resetPlay = false;\n\n\t\tif(valueTable.length<=Math.ceil(period)) {\n\t\n\t\t\tvalueTable.push(Math.round(Math.random())*2-1);\n\t\n\t\t\treturn valueTable[valueTable.length-1];\n\t\n\t\t} else {\n\t\n\t\t\tvalueTable[playVal] = (valueTable[playVal>=(valueTable.length-1)?0:playVal+1] + valueTable[playVal]) * 0.5;\n\t\n\t\t\tif(playVal>=Math.floor(period)) {\n\t\t\t\tif(playVal<Math.ceil(period)) {\n\t\t\t\t\tif((periodCount%100)>=p_hundredth) {\n\t\t\t\t\t\t// Reset\n\t\t\t\t\t\tresetPlay = true;\n\t\t\t\t\t\tvalueTable[playVal+1] = (valueTable[0] + valueTable[playVal+1]) * 0.5;\n\t\t\t\t\t\tvars.periodCount++;\t\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tresetPlay = true;\t\n\t\t\t\t}\n\t\t\t}\n\t\n\t\t\tvar _return = valueTable[playVal];\n\t\t\tif(resetPlay) { vars.playVal = 0; } else { vars.playVal++; }\n\t\n\t\t\treturn _return;\n\t\n\t\t}\n\t}\n},\n{\n\tname: 'edm',\n\tattack:\tfunction() { return 0.002; },\n\tdampen: function() { return 1; },\n\twave: function(i, sampleRate, frequency) {\n\t\tvar base = this.modulate[0];\n\t\tvar mod = this.modulate.slice(1);\n\t\treturn mod[0](\n\t\t\ti,\n\t\t\tsampleRate,\n\t\t\tfrequency,\n\t\t\tmod[9](\n\t\t\t\ti,\n\t\t\t\tsampleRate,\n\t\t\t\tfrequency,\n\t\t\t\tmod[2](\n\t\t\t\t\ti,\n\t\t\t\t\tsampleRate,\n\t\t\t\t\tfrequency,\n\t\t\t\t\tMath.pow(base(i, sampleRate, frequency, 0), 3) +\n\t\t\t\t\t\tMath.pow(base(i, sampleRate, frequency, 0.5), 5) +\n\t\t\t\t\t\tMath.pow(base(i, sampleRate, frequency, 1), 7)\n\t\t\t\t)\n\t\t\t) +\n\t\t\t\tmod[8](\n\t\t\t\t\ti,\n\t\t\t\t\tsampleRate,\n\t\t\t\t\tfrequency,\n\t\t\t\t\tbase(i, sampleRate, frequency, 1.75)\n\t\t\t\t)\n\t\t);\n\t}\n});\n"
  },
  {
    "path": "editor/js/libs/gl-matrix-min.js",
    "content": "/*!\n@fileoverview gl-matrix - High performance matrix and vector operations\n@author Brandon Jones\n@author Colin MacKenzie IV\n@version 2.7.0\n\nCopyright (c) 2015-2018, Brandon Jones, Colin MacKenzie IV.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n*/\n!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;i<s;i+=r)t[0]=n[i],t[1]=n[i+1],t[2]=n[i+2],t[3]=n[i+3],u(t,t,o),n[i]=t[0],n[i+1]=t[1],n[i+2]=t[2],n[i+3]=t[3];return n}}()},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(3);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n},n.length=u,n.fromValues=o,n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t},n.set=function(t,n,r,a){return t[0]=n,t[1]=r,t[2]=a,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},n.subtract=i,n.multiply=s,n.divide=c,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},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},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},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},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},n.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*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},n.distance=f,n.squaredDistance=M,n.squaredLength=h,n.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t},n.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t[2]=1/n[2],t},n.normalize=l,n.dot=v,n.cross=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],s=r[2];return t[0]=e*s-u*i,t[1]=u*o-a*s,t[2]=a*i-e*o,t},n.lerp=function(t,n,r,a){var e=n[0],u=n[1],o=n[2];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t[2]=o+a*(r[2]-o),t},n.hermite=function(t,n,r,a,e,u){var o=u*u,i=o*(2*u-3)+1,s=o*(u-2)+u,c=o*(u-1),f=o*(3-2*u);return t[0]=n[0]*i+r[0]*s+a[0]*c+e[0]*f,t[1]=n[1]*i+r[1]*s+a[1]*c+e[1]*f,t[2]=n[2]*i+r[2]*s+a[2]*c+e[2]*f,t},n.bezier=function(t,n,r,a,e,u){var o=1-u,i=o*o,s=u*u,c=i*o,f=3*u*i,M=3*s*o,h=s*u;return t[0]=n[0]*c+r[0]*f+a[0]*M+e[0]*h,t[1]=n[1]*c+r[1]*f+a[1]*M+e[1]*h,t[2]=n[2]*c+r[2]*f+a[2]*M+e[2]*h,t},n.random=function(t,n){n=n||1;var r=2*a.RANDOM()*Math.PI,e=2*a.RANDOM()-1,u=Math.sqrt(1-e*e)*n;return t[0]=Math.cos(r)*u,t[1]=Math.sin(r)*u,t[2]=e*n,t},n.transformMat4=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[3]*a+r[7]*e+r[11]*u+r[15];return o=o||1,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},n.transformMat3=function(t,n,r){var a=n[0],e=n[1],u=n[2];return t[0]=a*r[0]+e*r[3]+u*r[6],t[1]=a*r[1]+e*r[4]+u*r[7],t[2]=a*r[2]+e*r[5]+u*r[8],t},n.transformQuat=function(t,n,r){var a=r[0],e=r[1],u=r[2],o=r[3],i=n[0],s=n[1],c=n[2],f=e*c-u*s,M=u*i-a*c,h=a*s-e*i,l=e*h-u*M,v=u*f-a*h,d=a*M-e*f,b=2*o;return f*=b,M*=b,h*=b,l*=2,v*=2,d*=2,t[0]=i+f+l,t[1]=s+M+v,t[2]=c+h+d,t},n.rotateX=function(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[0],u[1]=e[1]*Math.cos(a)-e[2]*Math.sin(a),u[2]=e[1]*Math.sin(a)+e[2]*Math.cos(a),t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t},n.rotateY=function(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[2]*Math.sin(a)+e[0]*Math.cos(a),u[1]=e[1],u[2]=e[2]*Math.cos(a)-e[0]*Math.sin(a),t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t},n.rotateZ=function(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[0]*Math.cos(a)-e[1]*Math.sin(a),u[1]=e[0]*Math.sin(a)+e[1]*Math.cos(a),u[2]=e[2],t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t},n.angle=function(t,n){var r=o(t[0],t[1],t[2]),a=o(n[0],n[1],n[2]);l(r,r),l(a,a);var e=v(r,a);return e>1?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;i<s;i+=r)t[0]=n[i],t[1]=n[i+1],t[2]=n[i+2],u(t,t,o),n[i]=t[0],n[i+1]=t[1],n[i+2]=t[2];return n}}()},function(t,n,r){\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.setAxes=n.sqlerp=n.rotationTo=n.equals=n.exactEquals=n.normalize=n.sqrLen=n.squaredLength=n.len=n.length=n.lerp=n.dot=n.scale=n.mul=n.add=n.set=n.copy=n.fromValues=n.clone=void 0,n.create=s,n.identity=function(t){return t[0]=0,t[1]=0,t[2]=0,t[3]=1,t},n.setAxisAngle=c,n.getAxisAngle=function(t,n){var r=2*Math.acos(n[3]),e=Math.sin(r/2);e>a.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(s<a.EPSILON)return null;u*=s=1/s,o*=s,i*=s,c=Math.sin(r),f=Math.cos(r),M=1-f,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],E=n[9],O=n[10],R=n[11],y=u*u*M+f,q=o*u*M+i*c,x=i*u*M-o*c,_=u*o*M-i*c,Y=o*o*M+f,L=i*o*M+u*c,S=u*i*M+o*c,w=o*i*M-u*c,I=i*i*M+f,t[0]=h*y+b*q+A*x,t[1]=l*y+m*q+E*x,t[2]=v*y+p*q+O*x,t[3]=d*y+P*q+R*x,t[4]=h*_+b*Y+A*L,t[5]=l*_+m*Y+E*L,t[6]=v*_+p*Y+O*L,t[7]=d*_+P*Y+R*L,t[8]=h*S+b*w+A*I,t[9]=l*S+m*w+E*I,t[10]=v*S+p*w+O*I,t[11]=d*S+P*w+R*I,n!==t&&(t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15]);return t},n.rotateX=function(t,n,r){var a=Math.sin(r),e=Math.cos(r),u=n[4],o=n[5],i=n[6],s=n[7],c=n[8],f=n[9],M=n[10],h=n[11];n!==t&&(t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15]);return t[4]=u*e+c*a,t[5]=o*e+f*a,t[6]=i*e+M*a,t[7]=s*e+h*a,t[8]=c*e-u*a,t[9]=f*e-o*a,t[10]=M*e-i*a,t[11]=h*e-s*a,t},n.rotateY=function(t,n,r){var a=Math.sin(r),e=Math.cos(r),u=n[0],o=n[1],i=n[2],s=n[3],c=n[8],f=n[9],M=n[10],h=n[11];n!==t&&(t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15]);return t[0]=u*e-c*a,t[1]=o*e-f*a,t[2]=i*e-M*a,t[3]=s*e-h*a,t[8]=u*a+c*e,t[9]=o*a+f*e,t[10]=i*a+M*e,t[11]=s*a+h*e,t},n.rotateZ=function(t,n,r){var a=Math.sin(r),e=Math.cos(r),u=n[0],o=n[1],i=n[2],s=n[3],c=n[4],f=n[5],M=n[6],h=n[7];n!==t&&(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]);return t[0]=u*e+c*a,t[1]=o*e+f*a,t[2]=i*e+M*a,t[3]=s*e+h*a,t[4]=c*e-u*a,t[5]=f*e-o*a,t[6]=M*e-i*a,t[7]=h*e-s*a,t},n.fromTranslation=function(t,n){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]=n[0],t[13]=n[1],t[14]=n[2],t[15]=1,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=n[1],t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=n[2],t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.fromRotation=function(t,n,r){var e=r[0],u=r[1],o=r[2],i=Math.sqrt(e*e+u*u+o*o),s=void 0,c=void 0,f=void 0;if(i<a.EPSILON)return null;return e*=i=1/i,u*=i,o*=i,s=Math.sin(n),c=Math.cos(n),f=1-c,t[0]=e*e*f+c,t[1]=u*e*f+o*s,t[2]=o*e*f-u*s,t[3]=0,t[4]=e*u*f-o*s,t[5]=u*u*f+c,t[6]=o*u*f+e*s,t[7]=0,t[8]=e*o*f+u*s,t[9]=u*o*f-e*s,t[10]=o*o*f+c,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.fromXRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=a,t[6]=r,t[7]=0,t[8]=0,t[9]=-r,t[10]=a,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.fromYRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=0,t[2]=-r,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=r,t[9]=0,t[10]=a,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.fromZRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=0,t[3]=0,t[4]=-r,t[5]=a,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},n.fromRotationTranslation=o,n.fromQuat2=function(t,n){var r=new a.ARRAY_TYPE(3),e=-n[0],u=-n[1],i=-n[2],s=n[3],c=n[4],f=n[5],M=n[6],h=n[7],l=e*e+u*u+i*i+s*s;l>0?(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)<a.EPSILON&&Math.abs(m-R)<a.EPSILON&&Math.abs(p-y)<a.EPSILON)return e(t);h=b-O,l=m-R,v=p-y,d=1/Math.sqrt(h*h+l*l+v*v),o=A*(v*=d)-E*(l*=d),i=E*(h*=d)-P*v,s=P*l-A*h,(d=Math.sqrt(o*o+i*i+s*s))?(o*=d=1/d,i*=d,s*=d):(o=0,i=0,s=0);c=l*s-v*i,f=v*o-h*s,M=h*i-l*o,(d=Math.sqrt(c*c+f*f+M*M))?(c*=d=1/d,f*=d,M*=d):(c=0,f=0,M=0);return t[0]=o,t[1]=c,t[2]=h,t[3]=0,t[4]=i,t[5]=f,t[6]=l,t[7]=0,t[8]=s,t[9]=M,t[10]=v,t[11]=0,t[12]=-(o*b+i*m+s*p),t[13]=-(c*b+f*m+M*p),t[14]=-(h*b+l*m+v*p),t[15]=1,t},n.targetTo=function(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=a[0],s=a[1],c=a[2],f=e-r[0],M=u-r[1],h=o-r[2],l=f*f+M*M+h*h;l>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;i<s;i+=r)t[0]=n[i],t[1]=n[i+1],u(t,t,o),n[i]=t[0],n[i+1]=t[1];return n}}()},function(t,n,r){\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.sqrLen=n.squaredLength=n.len=n.length=n.dot=n.mul=n.setReal=n.getReal=void 0,n.create=function(){var t=new a.ARRAY_TYPE(8);a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0,t[4]=0,t[5]=0,t[6]=0,t[7]=0);return t[3]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(8);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},n.fromValues=function(t,n,r,e,u,o,i,s){var c=new a.ARRAY_TYPE(8);return c[0]=t,c[1]=n,c[2]=r,c[3]=e,c[4]=u,c[5]=o,c[6]=i,c[7]=s,c},n.fromRotationTranslationValues=function(t,n,r,e,u,o,i){var s=new a.ARRAY_TYPE(8);s[0]=t,s[1]=n,s[2]=r,s[3]=e;var c=.5*u,f=.5*o,M=.5*i;return s[4]=c*e+f*r-M*n,s[5]=f*e+M*t-c*r,s[6]=M*e+c*n-f*t,s[7]=-c*t-f*n-M*r,s},n.fromRotationTranslation=i,n.fromTranslation=function(t,n){return t[0]=0,t[1]=0,t[2]=0,t[3]=1,t[4]=.5*n[0],t[5]=.5*n[1],t[6]=.5*n[2],t[7]=0,t},n.fromRotation=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=0,t[5]=0,t[6]=0,t[7]=0,t},n.fromMat4=function(t,n){var r=e.create();u.getRotation(r,n);var o=new a.ARRAY_TYPE(3);return u.getTranslation(o,n),i(t,r,o),t},n.copy=s,n.identity=function(t){return t[0]=0,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t[6]=0,t[7]=0,t},n.set=function(t,n,r,a,e,u,o,i,s){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},n.getDual=function(t,n){return t[0]=n[4],t[1]=n[5],t[2]=n[6],t[3]=n[7],t},n.setDual=function(t,n){return t[4]=n[0],t[5]=n[1],t[6]=n[2],t[7]=n[3],t},n.getTranslation=function(t,n){var r=n[4],a=n[5],e=n[6],u=n[7],o=-n[0],i=-n[1],s=-n[2],c=n[3];return t[0]=2*(r*c+u*o+a*s-e*i),t[1]=2*(a*c+u*i+e*o-r*s),t[2]=2*(e*c+u*s+r*i-a*o),t},n.translate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=.5*r[0],s=.5*r[1],c=.5*r[2],f=n[4],M=n[5],h=n[6],l=n[7];return t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=o*i+e*c-u*s+f,t[5]=o*s+u*i-a*c+M,t[6]=o*c+a*s-e*i+h,t[7]=-a*i-e*s-u*c+l,t},n.rotateX=function(t,n,r){var a=-n[0],u=-n[1],o=-n[2],i=n[3],s=n[4],c=n[5],f=n[6],M=n[7],h=s*i+M*a+c*o-f*u,l=c*i+M*u+f*a-s*o,v=f*i+M*o+s*u-c*a,d=M*i-s*a-c*u-f*o;return e.rotateX(t,n,r),a=t[0],u=t[1],o=t[2],i=t[3],t[4]=h*i+d*a+l*o-v*u,t[5]=l*i+d*u+v*a-h*o,t[6]=v*i+d*o+h*u-l*a,t[7]=d*i-h*a-l*u-v*o,t},n.rotateY=function(t,n,r){var a=-n[0],u=-n[1],o=-n[2],i=n[3],s=n[4],c=n[5],f=n[6],M=n[7],h=s*i+M*a+c*o-f*u,l=c*i+M*u+f*a-s*o,v=f*i+M*o+s*u-c*a,d=M*i-s*a-c*u-f*o;return e.rotateY(t,n,r),a=t[0],u=t[1],o=t[2],i=t[3],t[4]=h*i+d*a+l*o-v*u,t[5]=l*i+d*u+v*a-h*o,t[6]=v*i+d*o+h*u-l*a,t[7]=d*i-h*a-l*u-v*o,t},n.rotateZ=function(t,n,r){var a=-n[0],u=-n[1],o=-n[2],i=n[3],s=n[4],c=n[5],f=n[6],M=n[7],h=s*i+M*a+c*o-f*u,l=c*i+M*u+f*a-s*o,v=f*i+M*o+s*u-c*a,d=M*i-s*a-c*u-f*o;return e.rotateZ(t,n,r),a=t[0],u=t[1],o=t[2],i=t[3],t[4]=h*i+d*a+l*o-v*u,t[5]=l*i+d*u+v*a-h*o,t[6]=v*i+d*o+h*u-l*a,t[7]=d*i-h*a-l*u-v*o,t},n.rotateByQuatAppend=function(t,n,r){var a=r[0],e=r[1],u=r[2],o=r[3],i=n[0],s=n[1],c=n[2],f=n[3];return t[0]=i*o+f*a+s*u-c*e,t[1]=s*o+f*e+c*a-i*u,t[2]=c*o+f*u+i*e-s*a,t[3]=f*o-i*a-s*e-c*u,i=n[4],s=n[5],c=n[6],f=n[7],t[4]=i*o+f*a+s*u-c*e,t[5]=s*o+f*e+c*a-i*u,t[6]=c*o+f*u+i*e-s*a,t[7]=f*o-i*a-s*e-c*u,t},n.rotateByQuatPrepend=function(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,i=r[4],s=r[5],c=r[6],f=r[7],t[4]=a*f+o*i+e*c-u*s,t[5]=e*f+o*s+u*i-a*c,t[6]=u*f+o*c+a*s-e*i,t[7]=o*f-a*i-e*s-u*c,t},n.rotateAroundAxis=function(t,n,r,e){if(Math.abs(e)<a.EPSILON)return s(t,n);var u=Math.sqrt(r[0]*r[0]+r[1]*r[1]+r[2]*r[2]);e*=.5;var o=Math.sin(e),i=o*r[0]/u,c=o*r[1]/u,f=o*r[2]/u,M=Math.cos(e),h=n[0],l=n[1],v=n[2],d=n[3];t[0]=h*M+d*i+l*f-v*c,t[1]=l*M+d*c+v*i-h*f,t[2]=v*M+d*f+h*c-l*i,t[3]=d*M-h*i-l*c-v*f;var b=n[4],m=n[5],p=n[6],P=n[7];return t[4]=b*M+P*i+m*f-p*c,t[5]=m*M+P*c+p*i-b*f,t[6]=p*M+P*f+b*c-m*i,t[7]=P*M-b*i-m*c-p*f,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[4]=n[4]+r[4],t[5]=n[5]+r[5],t[6]=n[6]+r[6],t[7]=n[7]+r[7],t},n.multiply=c,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[4]=n[4]*r,t[5]=n[5]*r,t[6]=n[6]*r,t[7]=n[7]*r,t},n.lerp=function(t,n,r,a){var e=1-a;f(n,r)<0&&(a=-a);return t[0]=n[0]*e+r[0]*a,t[1]=n[1]*e+r[1]*a,t[2]=n[2]*e+r[2]*a,t[3]=n[3]*e+r[3]*a,t[4]=n[4]*e+r[4]*a,t[5]=n[5]*e+r[5]*a,t[6]=n[6]*e+r[6]*a,t[7]=n[7]*e+r[7]*a,t},n.invert=function(t,n){var r=h(n);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},n.conjugate=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},n.normalize=function(t,n){var r=h(n);if(r>0){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}])});"
  },
  {
    "path": "editor/js/libs/litegl.js",
    "content": "//packer version\n//litegl.js by Javi Agenjo 2014 @tamat (tamats.com)\n//forked from lightgl.js by Evan Wallace (madebyevan.com)\n\"use strict\";\n\n(function(global){\n\nvar GL = global.GL = {};\n\nif(typeof(glMatrix) == \"undefined\")\n\tthrow(\"litegl.js requires gl-matrix to work. It must be included before litegl.\");\nelse\n{\n\tif(!global.vec2)\n\t\tthrow(\"litegl.js does not support gl-matrix 3.0, download 2.8 https://github.com/toji/gl-matrix/releases/tag/v2.8.1\");\n}\n\n//polyfill\nglobal.requestAnimationFrame = global.requestAnimationFrame || global.mozRequestAnimationFrame || global.webkitRequestAnimationFrame || function(callback) { setTimeout(callback, 1000 / 60); };\n\nGL.blockable_keys = {\"Up\":true,\"Down\":true,\"Left\":true,\"Right\":true};\n\nGL.reverse = null;\n\n//some consts\n//https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button\nGL.LEFT_MOUSE_BUTTON = 0;\nGL.MIDDLE_MOUSE_BUTTON = 1;\nGL.RIGHT_MOUSE_BUTTON = 2;\n\nGL.LEFT_MOUSE_BUTTON_MASK = 1;\nGL.RIGHT_MOUSE_BUTTON_MASK = 2;\nGL.MIDDLE_MOUSE_BUTTON_MASK = 4;\n\nGL.last_context_id = 0;\n\n\n//Define WEBGL ENUMS as statics (more to come in WebGL 2)\n//sometimes we need some gl enums before having the gl context, solution: define them globally because the specs says they are constant)\n\nGL.COLOR_BUFFER_BIT = 16384;\nGL.DEPTH_BUFFER_BIT = 256;\nGL.STENCIL_BUFFER_BIT = 1024;\n\nGL.TEXTURE_2D = 3553;\nGL.TEXTURE_CUBE_MAP = 34067;\nGL.TEXTURE_3D = 32879;\n\nGL.TEXTURE_MAG_FILTER = 10240;\nGL.TEXTURE_MIN_FILTER = 10241;\nGL.TEXTURE_WRAP_S = 10242;\nGL.TEXTURE_WRAP_T = 10243;\n\nGL.BYTE = 5120;\nGL.UNSIGNED_BYTE = 5121;\nGL.SHORT = 5122;\nGL.UNSIGNED_SHORT = 5123;\nGL.INT = 5124;\nGL.UNSIGNED_INT = 5125;\nGL.FLOAT = 5126;\nGL.HALF_FLOAT_OES = 36193; //webgl 1.0 only\n\n//webgl2 formats\nGL.HALF_FLOAT = 5131; \nGL.DEPTH_COMPONENT16 = 33189;\nGL.DEPTH_COMPONENT24 = 33190;\nGL.DEPTH_COMPONENT32F = 36012;\n\nGL.FLOAT_VEC2 = 35664;\nGL.FLOAT_VEC3 = 35665;\nGL.FLOAT_VEC4 = 35666;\nGL.INT_VEC2 = 35667;\nGL.INT_VEC3 = 35668;\nGL.INT_VEC4 = 35669;\nGL.BOOL = 35670;\nGL.BOOL_VEC2 = 35671;\nGL.BOOL_VEC3 = 35672;\nGL.BOOL_VEC4 = 35673;\nGL.FLOAT_MAT2 = 35674;\nGL.FLOAT_MAT3 = 35675;\nGL.FLOAT_MAT4 = 35676;\n\n//used to know the amount of data to reserve per uniform\nGL.TYPE_LENGTH = {};\nGL.TYPE_LENGTH[ GL.FLOAT ] = GL.TYPE_LENGTH[ GL.INT ] = GL.TYPE_LENGTH[ GL.BYTE ] = GL.TYPE_LENGTH[ GL.BOOL ] = 1;\nGL.TYPE_LENGTH[ GL.FLOAT_VEC2 ] = GL.TYPE_LENGTH[ GL.INT_VEC2 ] = GL.TYPE_LENGTH[ GL.BOOL_VEC2 ] = 2;\nGL.TYPE_LENGTH[ GL.FLOAT_VEC3 ] = GL.TYPE_LENGTH[ GL.INT_VEC3 ] = GL.TYPE_LENGTH[ GL.BOOL_VEC3 ] = 3;\nGL.TYPE_LENGTH[ GL.FLOAT_VEC4 ] = GL.TYPE_LENGTH[ GL.INT_VEC4 ] = GL.TYPE_LENGTH[ GL.BOOL_VEC4 ] = 4;\nGL.TYPE_LENGTH[ GL.FLOAT_MAT3 ] = 9;\nGL.TYPE_LENGTH[ GL.FLOAT_MAT4 ] = 16;\n\n\nGL.SAMPLER_2D = 35678;\nGL.SAMPLER_3D = 35679;\nGL.SAMPLER_CUBE = 35680;\n\nGL.DEPTH_COMPONENT = 6402;\nGL.ALPHA = 6406;\nGL.RGB = 6407;\nGL.RGBA = 6408;\nGL.LUMINANCE = 6409;\nGL.LUMINANCE_ALPHA = 6410;\nGL.DEPTH_STENCIL = 34041;\nGL.UNSIGNED_INT_24_8_WEBGL = 34042;\n\n//webgl2 formats\nGL.R8 = 33321;\nGL.R16F = 33325;\nGL.R32F = 33326;\nGL.R8UI = 33330;\nGL.RG8 = 33323;\nGL.RG16F = 33327;\nGL.RG32F = 33328;\nGL.RGB8 = 32849;\nGL.SRGB8 = 35905;\nGL.RGB565 = 36194;\nGL.R11F_G11F_B10F = 35898;\nGL.RGB9_E5 = 35901;\nGL.RGB16F = 34843;\nGL.RGB32F = 34837;\nGL.RGB8UI = 36221;\nGL.RGBA8 = 32856;\nGL.RGB5_A1 = 32855;\nGL.RGBA16F = 34842;\nGL.RGBA32F = 34836;\nGL.RGBA8UI = 36220;\nGL.RGBA16I = 36232;\nGL.RGBA16UI = 36214;\nGL.RGBA32I = 36226;\nGL.RGBA32UI = 36208;\n\nGL.NEAREST = 9728;\nGL.LINEAR = 9729;\nGL.NEAREST_MIPMAP_NEAREST = 9984;\nGL.LINEAR_MIPMAP_NEAREST = 9985;\nGL.NEAREST_MIPMAP_LINEAR = 9986;\nGL.LINEAR_MIPMAP_LINEAR = 9987;\n\nGL.REPEAT = 10497;\nGL.CLAMP_TO_EDGE = 33071;\nGL.MIRRORED_REPEAT = 33648;\n\nGL.ZERO = 0;\nGL.ONE = 1;\nGL.SRC_COLOR = 768;\nGL.ONE_MINUS_SRC_COLOR = 769;\nGL.SRC_ALPHA = 770;\nGL.ONE_MINUS_SRC_ALPHA = 771;\nGL.DST_ALPHA = 772;\nGL.ONE_MINUS_DST_ALPHA = 773;\nGL.DST_COLOR = 774;\nGL.ONE_MINUS_DST_COLOR = 775;\nGL.SRC_ALPHA_SATURATE = 776;\nGL.CONSTANT_COLOR = 32769;\nGL.ONE_MINUS_CONSTANT_COLOR = 32770;\nGL.CONSTANT_ALPHA = 32771;\nGL.ONE_MINUS_CONSTANT_ALPHA = 32772;\n\nGL.VERTEX_SHADER = 35633;\nGL.FRAGMENT_SHADER = 35632;\n\nGL.FRONT = 1028;\nGL.BACK = 1029;\nGL.FRONT_AND_BACK = 1032;\n\nGL.NEVER = 512;\nGL.LESS = 513;\nGL.EQUAL = 514;\nGL.LEQUAL = 515;\nGL.GREATER = 516;\nGL.NOTEQUAL = 517;\nGL.GEQUAL = 518;\nGL.ALWAYS = 519;\n\nGL.KEEP = 7680;\nGL.REPLACE = 7681;\nGL.INCR = 7682;\nGL.DECR = 7683;\nGL.INCR_WRAP = 34055;\nGL.DECR_WRAP = 34056;\nGL.INVERT = 5386;\n\nGL.STREAM_DRAW = 35040;\nGL.STATIC_DRAW = 35044;\nGL.DYNAMIC_DRAW = 35048;\n\nGL.ARRAY_BUFFER = 34962;\nGL.ELEMENT_ARRAY_BUFFER = 34963;\n\nGL.POINTS = 0;\nGL.LINES = 1;\nGL.LINE_LOOP = 2;\nGL.LINE_STRIP = 3;\nGL.TRIANGLES = 4;\nGL.TRIANGLE_STRIP = 5;\nGL.TRIANGLE_FAN = 6;\n\nGL.CW = 2304;\nGL.CCW = 2305;\n\nGL.CULL_FACE = 2884;\nGL.DEPTH_TEST = 2929;\nGL.BLEND = 3042;\n\nGL.temp_vec3 = vec3.create();\nGL.temp2_vec3 = vec3.create();\nGL.temp_vec4 = vec4.create();\nGL.temp_quat = quat.create();\nGL.temp_mat3 = mat3.create();\nGL.temp_mat4 = mat4.create();\n\n\r\nglobal.DEG2RAD = 0.0174532925;\r\nglobal.RAD2DEG = 57.295779578552306;\r\nglobal.EPSILON = 0.000001;\r\n\r\n/**\r\n* Tells if one number is power of two (used for textures)\r\n* @method isPowerOfTwo\r\n* @param {v} number\r\n* @return {boolean}\r\n*/\r\nglobal.isPowerOfTwo = GL.isPowerOfTwo = function isPowerOfTwo(v)\r\n{\r\n\treturn ((Math.log(v) / Math.log(2)) % 1) == 0;\r\n}\r\n\r\n/**\r\n* Tells if one number is power of two (used for textures)\r\n* @method isPowerOfTwo\r\n* @param {v} number\r\n* @return {boolean}\r\n*/\r\nglobal.nearestPowerOfTwo = GL.nearestPowerOfTwo = function nearestPowerOfTwo(v)\r\n{\r\n\treturn Math.pow(2, Math.round( Math.log( v ) / Math.log(2) ) )\r\n}\r\n\r\n\r\n/**\r\n* converts from polar to cartesian\r\n* @method polarToCartesian\r\n* @param {vec3} out\r\n* @param {number} azimuth orientation from 0 to 2PI\r\n* @param {number} inclianation from -PI to PI\r\n* @param {number} radius\r\n* @return {vec3} returns out\r\n*/\r\nglobal.polarToCartesian = function( out, azimuth, inclination, radius )\r\n{\r\n\tout = out || vec3.create();\r\n\tout[0] = radius * Math.sin(inclination) * Math.cos(azimuth);\r\n\tout[1] = radius * Math.cos(inclination);\r\n\tout[2] = radius * Math.sin(inclination) * Math.sin(azimuth);\r\n\treturn out;\r\n}\r\n\r\n/**\r\n* converts from cartesian to polar\r\n* @method cartesianToPolar\r\n* @param {vec3} out\r\n* @param {number} x\r\n* @param {number} y\r\n* @param {number} z\r\n* @return {vec3} returns [azimuth,inclination,radius]\r\n*/\r\nglobal.cartesianToPolar = function( out, x,y,z )\r\n{\r\n\tout = out || vec3.create();\r\n\tout[2] = Math.sqrt(x*x+y*y+z*z);\r\n\tout[0] = Math.atan2(x,z);\r\n\tout[1] = Math.acos(z/out[2]);\r\n\treturn out;\r\n}\r\n\r\n//Global Scope\r\n//better array conversion to string for serializing\r\nvar typed_arrays = [ Uint8Array, Int8Array, Uint16Array, Int16Array, Uint32Array, Int32Array, Float32Array, Float64Array ];\r\nfunction typedToArray(){ \r\n\treturn Array.prototype.slice.call(this);\r\n}\r\ntyped_arrays.forEach( function(v) { \r\n\tif(!v.prototype.toJSON)\r\n\t\tObject.defineProperty( v.prototype, \"toJSON\", {\r\n\t\t\tvalue: typedToArray,\r\n\t\t\tenumerable: false\r\n\t\t});\r\n});\r\n\r\n\r\n\r\n/**\r\n* Get current time in milliseconds\r\n* @method getTime\r\n* @return {number}\r\n*/\r\nif(typeof(performance) != \"undefined\")\r\n  global.getTime = performance.now.bind(performance);\r\nelse\r\n  global.getTime = Date.now.bind( Date );\r\nGL.getTime = global.getTime;\r\n\r\n\r\nglobal.isFunction = function isFunction(obj) {\r\n  return !!(obj && obj.constructor && obj.call && obj.apply);\r\n}\r\n\r\nglobal.isArray = function isArray(obj) {\r\n  return (obj && obj.constructor === Array );\r\n  //var str = Object.prototype.toString.call(obj);\r\n  //return str == '[object Array]' || str == '[object Float32Array]';\r\n}\r\n\r\nglobal.isNumber = function isNumber(obj) {\r\n  return (obj != null && obj.constructor === Number );\r\n}\r\n\r\nglobal.getClassName = function getClassName(obj)\r\n{\r\n\tif (!obj)\r\n\t\treturn;\r\n\r\n\t//from function info, but not standard\r\n\tif(obj.name)\r\n\t\treturn obj.name;\r\n\r\n\t//from sourcecode\r\n\tif(obj.toString) {\r\n\t\tvar arr = obj.toString().match(\r\n\t\t\t/function\\s*(\\w+)/);\r\n\t\tif (arr && arr.length == 2) {\r\n\t\t\treturn arr[1];\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/**\r\n* clone one object recursively, only allows objects containing number,strings,typed-arrays or other objects\r\n* @method cloneObject\r\n* @param {Object} object \r\n* @param {Object} target if omited an empty object is created\r\n* @return {Object}\r\n*/\r\nglobal.cloneObject = GL.cloneObject = function(o, t)\r\n{\r\n\tif(o.constructor !== Object)\r\n\t\tthrow(\"cloneObject only can clone pure javascript objects, not classes\");\r\n\r\n\tt = t || {};\r\n\r\n\tfor(var i in o)\r\n\t{\r\n\t\tvar v = o[i];\r\n\t\tif(v === null)\r\n\t\t{\r\n\t\t\tt[i] = null;\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\tswitch(v.constructor)\r\n\t\t{\r\n\t\t\tcase Int8Array:\r\n\t\t\tcase Uint8Array:\r\n\t\t\tcase Int16Array:\r\n\t\t\tcase Uint16Array:\r\n\t\t\tcase Int32Array:\r\n\t\t\tcase Uint32Array:\r\n\t\t\tcase Float32Array:\r\n\t\t\tcase Float64Array:\r\n\t\t\t\tt[i] = new v.constructor(v);\r\n\t\t\t\tbreak;\r\n\t\t\tcase Boolean:\r\n\t\t\tcase Number:\r\n\t\t\tcase String:\r\n\t\t\t\tt[i] = v;\r\n\t\t\t\tbreak;\r\n\t\t\tcase Array:\r\n\t\t\t\tt[i] = v.concat(); //content is not cloned\r\n\t\t\t\tbreak;\r\n\t\t\tcase Object:\r\n\t\t\t\tt[i] = GL.cloneObject(v);\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\treturn t;\r\n}\r\n\r\n\r\n/* SLOW because accepts booleans\r\nfunction isNumber(obj) {\r\n  var str = Object.prototype.toString.call(obj);\r\n  return str == '[object Number]' || str == '[object Boolean]';\r\n}\r\n*/\r\n\r\n//given a regular expression, a text and a callback, it calls the function every time it finds it\r\nglobal.regexMap = function regexMap(regex, text, callback) {\r\n  var result;\r\n  while ((result = regex.exec(text)) != null) {\r\n    callback(result);\r\n  }\r\n}\r\n\r\nglobal.createCanvas = GL.createCanvas = function createCanvas(width, height) {\r\n    var canvas = document.createElement('canvas');\r\n    canvas.width = width;\r\n    canvas.height = height;\r\n    return canvas;\r\n}\r\n\r\nglobal.cloneCanvas = GL.cloneCanvas = function cloneCanvas(c) {\r\n    var canvas = document.createElement('canvas');\r\n    canvas.width = c.width;\r\n    canvas.height = c.height;\r\n    var ctx = canvas.getContext(\"2d\");\r\n    ctx.drawImage(c,0,0);\r\n    return canvas;\r\n}\r\n\r\nif(typeof(Image) != \"undefined\") //not existing inside workers\r\n{\r\n\tImage.prototype.getPixels = function()\r\n\t{\r\n\t\tvar canvas = document.createElement('canvas');\r\n\t\tcanvas.width = this.width;\r\n\t\tcanvas.height = this.height;\r\n\t\tvar ctx = canvas.getContext(\"2d\");\r\n\t\tctx.drawImage(this,0,0);\r\n\t\treturn ctx.getImageData(0, 0, this.width, this.height).data;\r\n\t}\r\n}\r\n\r\n//you must pass an object with characters to replace and replace with what {\"a\":\"A\",\"c\":\"C\"}\r\nif(!String.prototype.hasOwnProperty(\"replaceAll\")) \r\n\tObject.defineProperty(String.prototype, \"replaceAll\", {\r\n\t\tvalue: function(words){\r\n\t\t\tvar str = this;\r\n\t\t\tfor(var i in words)\r\n\t\t\t\tstr = str.split(i).join(words[i]);\r\n\t\t\treturn str;\r\n\t\t},\r\n\t\tenumerable: false\r\n\t});\t\r\n\r\n/*\r\nString.prototype.replaceAll = function(words){\r\n\tvar str = this;\r\n\tfor(var i in words)\r\n\t\tstr = str.split(i).join(words[i]);\r\n    return str;\r\n};\r\n*/\r\n\r\n//used for hashing keys\r\nif(!String.prototype.hasOwnProperty(\"hashCode\")) \r\n\tObject.defineProperty(String.prototype, \"hashCode\", {\r\n\t\tvalue: function(){\r\n\t\t\tvar hash = 0, i, c, l;\r\n\t\t\tif (this.length == 0) return hash;\r\n\t\t\tfor (i = 0, l = this.length; i < l; ++i) {\r\n\t\t\t\tc  = this.charCodeAt(i);\r\n\t\t\t\thash  = ((hash<<5)-hash)+c;\r\n\t\t\t\thash |= 0; // Convert to 32bit integer\r\n\t\t\t}\r\n\t\t\treturn hash;\r\n\t\t},\r\n\t\tenumerable: false\r\n\t});\t\r\n\r\n//avoid errors when Typed array is expected and regular array is found\r\n//Array.prototype.subarray = Array.prototype.slice;\r\n//if(!Array.prototype.hasOwnProperty(\"subarray\"))\r\n//\tObject.defineProperty(Array.prototype, \"subarray\", { value: Array.prototype.slice, enumerable: false });\r\n\r\nif(!Array.prototype.hasOwnProperty(\"clone\"))\r\n\tObject.defineProperty(Array.prototype, \"clone\", { value: Array.prototype.concat, enumerable: false });\r\nif(!Float32Array.prototype.hasOwnProperty(\"clone\"))\r\n\tObject.defineProperty(Float32Array.prototype, \"clone\", { value: function() { return new Float32Array(this); }, enumerable: false });\r\n\r\n\r\n// remove all properties on obj, effectively reverting it to a new object (to reduce garbage)\r\nglobal.wipeObject = function wipeObject(obj)\r\n{\r\n  for (var p in obj)\r\n  {\r\n    if (obj.hasOwnProperty(p))\r\n      delete obj[p];\r\n  }\r\n};\r\n\r\n//copy methods from origin to target\r\nglobal.extendClass = GL.extendClass = function extendClass( target, origin ) {\r\n\tfor(var i in origin) //copy class properties\r\n\t{\r\n\t\tif(target.hasOwnProperty(i))\r\n\t\t\tcontinue;\r\n\t\ttarget[i] = origin[i];\r\n\t}\r\n\r\n\tif(origin.prototype) //copy prototype properties\r\n\t{\r\n\t\tvar prop_names = Object.getOwnPropertyNames( origin.prototype );\r\n\t\tfor(var i = 0; i < prop_names.length; ++i) //only enumerables\r\n\t\t{\r\n\t\t\tvar name = prop_names[i];\r\n\t\t\t//if(!origin.prototype.hasOwnProperty(name)) \r\n\t\t\t//\tcontinue;\r\n\r\n\t\t\tif(target.prototype.hasOwnProperty(name)) //avoid overwritting existing ones\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\t//copy getters \r\n\t\t\tif(origin.prototype.__lookupGetter__(name))\r\n\t\t\t\ttarget.prototype.__defineGetter__(name, origin.prototype.__lookupGetter__(name));\r\n\t\t\telse \r\n\t\t\t\ttarget.prototype[name] = origin.prototype[name];\r\n\r\n\t\t\t//and setters\r\n\t\t\tif(origin.prototype.__lookupSetter__(name))\r\n\t\t\t\ttarget.prototype.__defineSetter__(name, origin.prototype.__lookupSetter__(name));\r\n\t\t}\r\n\t}\r\n\r\n\tif(!target.hasOwnProperty(\"superclass\")) \r\n\t\tObject.defineProperty(target, \"superclass\", {\r\n\t\t\tget: function() { return origin },\r\n\t\t\tenumerable: false\r\n\t\t});\t\r\n}\r\n\r\n\r\n\r\n//simple http request\r\nglobal.HttpRequest = GL.request = function HttpRequest( url, params, callback, error, options )\r\n{\r\n\tvar async = true;\r\n\tif(options && options.async !== undefined)\r\n\t\tasync = options.async;\r\n\r\n\tif(params)\r\n\t{\r\n\t\tvar params_str = null;\r\n\t\tvar params_arr = [];\r\n\t\tfor(var i in params)\r\n\t\t\tparams_arr.push(i + \"=\" + params[i]);\r\n\t\tparams_str = params_arr.join(\"&\");\r\n\t\turl = url + \"?\" + params_str;\r\n\t}\r\n\r\n\tvar xhr = new XMLHttpRequest();\r\n\txhr.open('GET', url, async);\r\n\txhr.onload = function(e)\r\n\t{\r\n\t\tvar response = this.response;\r\n\t\tvar type = this.getResponseHeader(\"Content-Type\");\r\n\t\tif(this.status != 200)\r\n\t\t{\r\n\t\t\tLEvent.trigger(xhr,\"fail\",this.status);\r\n\t\t\tif(error)\r\n\t\t\t\terror(this.status);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tLEvent.trigger(xhr,\"done\",this.response);\r\n\t\tif(callback)\r\n\t\t\tcallback(this.response);\r\n\t\treturn;\r\n\t}\r\n\r\n\txhr.onerror = function(err)\r\n\t{\r\n\t\tLEvent.trigger(xhr,\"fail\",err);\r\n\t}\r\n\t\r\n\tif(options)\r\n\t{\r\n\t\tfor(var i in options)\r\n\t\t\txhr[i] = options[i];\r\n\t\tif(options.binary)\r\n\t\t\txhr.responseType = \"arraybuffer\";\r\n\t}\r\n\r\n\txhr.send();\r\n\r\n\treturn xhr;\r\n}\r\n\r\n//cheap simple promises\r\nif( global.XMLHttpRequest )\r\n{\r\n\tif( !XMLHttpRequest.prototype.hasOwnProperty(\"done\") )\r\n\t\tObject.defineProperty( XMLHttpRequest.prototype, \"done\", { enumerable: false, value: function(callback)\r\n\t\t{\r\n\t\t  LEvent.bind(this,\"done\", function(e,err) { callback(err); } );\r\n\t\t  return this;\r\n\t\t}});\r\n\r\n\tif( !XMLHttpRequest.prototype.hasOwnProperty(\"fail\") )\r\n\t\tObject.defineProperty( XMLHttpRequest.prototype, \"fail\", { enumerable: false, value: function(callback)\r\n\t\t{\r\n\t\t  LEvent.bind(this,\"fail\", function(e,err) { callback(err); } );\r\n\t\t  return this;\r\n\t\t}});\r\n}\r\n\r\nglobal.getFileExtension = function getFileExtension(url)\r\n{\r\n\tvar question = url.indexOf(\"?\");\r\n\tif(question != -1)\r\n\t\turl = url.substr(0,question);\r\n\tvar point = url.lastIndexOf(\".\");\r\n\tif(point == -1) \r\n\t\treturn \"\";\r\n\treturn url.substr(point+1).toLowerCase();\r\n} \r\n\r\n\r\n//allows to pack several (text)files inside one single file (useful for shaders)\r\n//every file must start with \\filename.ext  or /filename.ext\r\nglobal.loadFileAtlas = GL.loadFileAtlas = function loadFileAtlas(url, callback, sync)\r\n{\r\n\tvar deferred_callback = null;\r\n\r\n\tHttpRequest(url, null, function(data) {\r\n\t\tvar files = GL.processFileAtlas(data); \r\n\t\tif(callback)\r\n\t\t\tcallback(files);\r\n\t\tif(deferred_callback)\r\n\t\t\tdeferred_callback(files);\r\n\t}, alert, sync);\r\n\r\n\treturn { done: function(callback) { deferred_callback = callback; } };\r\n}\r\n\r\n//This parses a text file that contains several text files (they are separated by \"\\filename\"), and returns an object with every file separatly\r\nglobal.processFileAtlas = GL.processFileAtlas = function(data, skip_trim)\r\n{\r\n\tvar lines = data.split(\"\\n\");\r\n\tvar files = {};\r\n\r\n\tvar current_file_lines = [];\r\n\tvar current_file_name = \"\";\r\n\tfor(var i = 0, l = lines.length; i < l; i++)\r\n\t{\r\n\t\tvar line = skip_trim ? lines[i] : lines[i].trim();\r\n\t\tif(!line.length)\r\n\t\t\tcontinue;\r\n\t\tif( line[0] != \"\\\\\")\r\n\t\t{\r\n\t\t\tcurrent_file_lines.push(line);\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\tif( current_file_lines.length )\r\n\t\t\tfiles[ current_file_name ] = current_file_lines.join(\"\\n\");\r\n\t\tcurrent_file_lines.length = 0;\r\n\t\tcurrent_file_name = line.substr(1);\r\n\t}\r\n\r\n\tif( current_file_lines.length )\r\n\t\tfiles[ current_file_name ] = current_file_lines.join(\"\\n\");\r\n\r\n\treturn files;\r\n}\r\n\r\n\r\n/*\r\nglobal.halfFloatToFloat = function( h )\r\n{\r\n\tfunction convertMantissa(i) {\r\n\t    if (i == 0) \r\n\t\t\treturn 0\r\n\t\telse if (i < 1024)\r\n\t\t{\r\n\t        var m = i << 13;\r\n\t\t\tvar e = 0;\r\n\t\t\twhile (!(m & 0x00800000))\r\n\t\t\t{\r\n\t\t\t\te -= 0x00800000\r\n\t\t\t\tm = m << 1\r\n\t\t\t}\r\n\t        m &= ~0x00800000\r\n\t\t    e += 0x38800000\r\n\t        return m | e;\r\n\t\t}\r\n\t\treturn 0x38000000 + ((i - 1024) << 13);\r\n\t}\r\n\r\n\tfunction convertExponent(i)\t{\r\n\t\tif (i == 0)\r\n\t\t\treturn 0;\r\n\t\telse if (i >= 1 && i <= 31)\r\n\t\t\treturn i << 23;\r\n\t\telse if (i == 31)\r\n\t\t\treturn 0x47800000;\r\n\t\telse if (i == 32)\r\n\t\t\treturn 0x80000000;\r\n\t\telse if (i >= 33 && i <= 63)\r\n\t\t\treturn 0x80000000 + ((i - 32) << 23);\r\n\t\treturn 0xC7800000;\r\n\t}\r\n\r\n\tfunction convertOffset(i) {\r\n\t    if (i == 0 || i == 32)\r\n\t\t    return 0\r\n\t\treturn 1024;\r\n\t}\r\n\r\n\tvar v = convertMantissa( convertOffset( h >> 10) + (h & 0x3ff) ) + convertExponent(h >> 10);\r\n\tvar a = new Uint32Array([v]);\r\n\treturn (new Float32Array(a.buffer))[0]; \r\n}\r\n*/\r\n\r\nglobal.typedArrayToArray = function(array)\r\n{\r\n\tvar r = [];\r\n\tr.length = array.length;\r\n\tfor(var i = 0; i < array.length; i++)\r\n\t\tr[i] = array[i];\r\n\treturn r;\r\n}\r\n\r\nglobal.RGBToHex = function(r, g, b) { \r\n\tr = Math.min(255, r*255)|0;\r\n\tg = Math.min(255, g*255)|0;\r\n\tb = Math.min(255, b*255)|0;\r\n\treturn \"#\" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);\r\n}\r\n\r\nglobal.HUEToRGB = function ( p, q, t ){\r\n\tif(t < 0) t += 1;\r\n\tif(t > 1) t -= 1;\r\n\tif(t < 1/6) return p + (q - p) * 6 * t;\r\n\tif(t < 1/2) return q;\r\n\tif(t < 2/3) return p + (q - p) * (2/3 - t) * 6;\r\n\treturn p;\r\n}\r\n\r\nglobal.HSLToRGB = function( h, s, l, out ){\r\n\tvar r, g, b;\r\n\tout = out || vec3.create();\r\n\tif(s == 0){\r\n\t\tr = g = b = l; // achromatic\r\n\t}else{\r\n\t\tvar q = l < 0.5 ? l * (1 + s) : l + s - l * s;\r\n\t\tvar p = 2 * l - q;\r\n\t\tr = HUEToRGB(p, q, h + 1/3);\r\n\t\tg = HUEToRGB(p, q, h);\r\n\t\tb = HUEToRGB(p, q, h - 1/3);\r\n\t}\r\n\tout[0] = r;\r\n\tout[1] = g;\r\n\tout[2] = b;\r\n\treturn out;\r\n}\r\n\r\nglobal.hexColorToRGBA = (function() {\r\n\t//to change the color: from http://www.w3schools.com/cssref/css_colorsfull.asp\r\n\tvar string_colors = {\r\n\t\twhite: [1,1,1],\r\n\t\tblack: [0,0,0],\r\n\t\tgray: [0.501960813999176, 0.501960813999176, 0.501960813999176],\r\n\t\tred: [1,0,0],\r\n\t\torange: [1, 0.6470588445663452, 0],\r\n\t\tpink: [1, 0.7529411911964417, 0.7960784435272217],\r\n\t\tgreen: [0, 0.501960813999176, 0],\r\n\t\tlime: [0,1,0],\r\n\t\tblue: [0,0,1],\r\n\t\tviolet: [0.9333333373069763, 0.5098039507865906, 0.9333333373069763],\r\n\t\tmagenta: [1,0,1],\r\n\t\tcyan: [0,1,1],\r\n\t\tyellow: [1,1,0],\r\n\t\tbrown: [0.6470588445663452, 0.16470588743686676, 0.16470588743686676],\r\n\t\tsilver: [0.7529411911964417, 0.7529411911964417, 0.7529411911964417],\r\n\t\tgold: [1, 0.843137264251709, 0],\r\n\t\ttransparent: [0,0,0,0]\r\n\t};\r\n\r\n\treturn function( hex, color, alpha )\r\n\t{\r\n\talpha = (alpha === undefined ? 1 : alpha);\r\n\tcolor = color || new Float32Array(4);\r\n\tcolor[3] = alpha;\r\n\r\n\tif(typeof(hex) != \"string\")\r\n\t\treturn color;\r\n\r\n\r\n\t//for those hardcoded colors\r\n\tvar col = string_colors[hex];\r\n\tif( col !== undefined )\r\n\t{\r\n\t\tcolor.set( col );\r\n\t\tif(color.length == 3)\r\n\t\t\tcolor[3] = alpha;\r\n\t\telse\r\n\t\t\tcolor[3] *= alpha;\r\n\t\treturn color;\r\n\t}\r\n\r\n\t//rgba colors\r\n\tvar pos = hex.indexOf(\"rgba(\");\r\n\tif(pos != -1)\r\n\t{\r\n\t\tvar str = hex.substr(5,hex.length-2);\r\n\t\tstr = str.split(\",\");\r\n\t\tcolor[0] = parseInt( str[0] ) / 255;\r\n\t\tcolor[1] = parseInt( str[1] ) / 255;\r\n\t\tcolor[2] = parseInt( str[2] ) / 255;\r\n\t\tcolor[3] = parseFloat( str[3] ) * alpha;\r\n\t\treturn color;\r\n\t}\r\n\r\n\tvar pos = hex.indexOf(\"hsla(\");\r\n\tif(pos != -1)\r\n\t{\r\n\t\tvar str = hex.substr(5,hex.length-2);\r\n\t\tstr = str.split(\",\");\r\n\t\tHSLToRGB( parseInt( str[0] ) / 360, parseInt( str[1] ) / 100, parseInt( str[2] ) / 100, color );\r\n\t\tcolor[3] = parseFloat( str[3] ) * alpha;\r\n\t\treturn color;\r\n\t}\r\n\r\n\tcolor[3] = alpha;\r\n\r\n\t//rgb colors\r\n\tvar pos = hex.indexOf(\"rgb(\");\r\n\tif(pos != -1)\r\n\t{\r\n\t\tvar str = hex.substr(4,hex.length-2);\r\n\t\tstr = str.split(\",\");\r\n\t\tcolor[0] = parseInt( str[0] ) / 255;\r\n\t\tcolor[1] = parseInt( str[1] ) / 255;\r\n\t\tcolor[2] = parseInt( str[2] ) / 255;\r\n\t\treturn color;\r\n\t}\r\n\r\n\tvar pos = hex.indexOf(\"hsl(\");\r\n\tif(pos != -1)\r\n\t{\r\n\t\tvar str = hex.substr(4,hex.length-2);\r\n\t\tstr = str.split(\",\");\r\n\t\tHSLToRGB( parseInt( str[0] ) / 360, parseInt( str[1] ) / 100, parseInt( str[2] ) / 100, color );\r\n\t\treturn color;\r\n\t}\r\n\r\n\r\n\t//the rest\r\n\t// Expand shorthand form (e.g. \"03F\") to full form (e.g. \"0033FF\")\r\n\tvar shorthandRegex = /^#?([a-f\\d])([a-f\\d])([a-f\\d])$/i;\r\n\thex = hex.replace( shorthandRegex, function(m, r, g, b) {\r\n\t\treturn r + r + g + g + b + b;\r\n\t});\r\n\r\n\tvar result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\r\n\tif(!result)\r\n\t\treturn color;\r\n\r\n\tcolor[0] = parseInt(result[1], 16) / 255;\r\n\tcolor[1] = parseInt(result[2], 16) / 255;\r\n\tcolor[2] = parseInt(result[3], 16) / 255;\r\n\treturn color;\r\n\t}\r\n})();\n/**\r\n * @fileoverview dds - Utilities for loading DDS texture files\r\n * @author Brandon Jones\r\n * @version 0.1\r\n */\r\n\r\n/*\r\n * Copyright (c) 2012 Brandon Jones\r\n *\r\n * This software is provided 'as-is', without any express or implied\r\n * warranty. In no event will the authors be held liable for any damages\r\n * arising from the use of this software.\r\n *\r\n * Permission is granted to anyone to use this software for any purpose,\r\n * including commercial applications, and to alter it and redistribute it\r\n * freely, subject to the following restrictions:\r\n *\r\n *    1. The origin of this software must not be misrepresented; you must not\r\n *    claim that you wrote the original software. If you use this software\r\n *    in a product, an acknowledgment in the product documentation would be\r\n *    appreciated but is not required.\r\n *\r\n *    2. Altered source versions must be plainly marked as such, and must not\r\n *    be misrepresented as being the original software.\r\n *\r\n *    3. This notice may not be removed or altered from any source\r\n *    distribution.\r\n */\r\n\r\nvar DDS = (function () {\r\n\r\n    \"use strict\";\r\n    \r\n    // All values and structures referenced from:\r\n    // http://msdn.microsoft.com/en-us/library/bb943991.aspx/\r\n    var DDS_MAGIC = 0x20534444;\r\n    \r\n    var DDSD_CAPS = 0x1,\r\n        DDSD_HEIGHT = 0x2,\r\n        DDSD_WIDTH = 0x4,\r\n        DDSD_PITCH = 0x8,\r\n        DDSD_PIXELFORMAT = 0x1000,\r\n        DDSD_MIPMAPCOUNT = 0x20000,\r\n        DDSD_LINEARSIZE = 0x80000,\r\n        DDSD_DEPTH = 0x800000;\r\n\r\n    var DDSCAPS_COMPLEX = 0x8,\r\n        DDSCAPS_MIPMAP = 0x400000,\r\n        DDSCAPS_TEXTURE = 0x1000;\r\n        \r\n    var DDSCAPS2_CUBEMAP = 0x200,\r\n        DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,\r\n        DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,\r\n        DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,\r\n        DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,\r\n        DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,\r\n        DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,\r\n        DDSCAPS2_VOLUME = 0x200000;\r\n\r\n    var DDPF_ALPHAPIXELS = 0x1,\r\n        DDPF_ALPHA = 0x2,\r\n        DDPF_FOURCC = 0x4,\r\n        DDPF_RGB = 0x40,\r\n        DDPF_YUV = 0x200,\r\n        DDPF_LUMINANCE = 0x20000;\r\n\r\n    function fourCCToInt32(value) {\r\n        return value.charCodeAt(0) +\r\n            (value.charCodeAt(1) << 8) +\r\n            (value.charCodeAt(2) << 16) +\r\n            (value.charCodeAt(3) << 24);\r\n    }\r\n\r\n    function int32ToFourCC(value) {\r\n        return String.fromCharCode(\r\n            value & 0xff,\r\n            (value >> 8) & 0xff,\r\n            (value >> 16) & 0xff,\r\n            (value >> 24) & 0xff\r\n        );\r\n    }\r\n\r\n    var FOURCC_DXT1 = fourCCToInt32(\"DXT1\");\r\n    var FOURCC_DXT3 = fourCCToInt32(\"DXT3\");\r\n    var FOURCC_DXT5 = fourCCToInt32(\"DXT5\");\r\n\r\n    var headerLengthInt = 31; // The header length in 32 bit ints\r\n\r\n    // Offsets into the header array\r\n    var off_magic = 0;\r\n\r\n    var off_size = 1;\r\n    var off_flags = 2;\r\n    var off_height = 3;\r\n    var off_width = 4;\r\n\r\n    var off_mipmapCount = 7;\r\n\r\n    var off_pfFlags = 20;\r\n    var off_pfFourCC = 21;\r\n    var off_caps = 27;\r\n    \r\n    // Little reminder for myself where the above values come from\r\n    /*DDS_PIXELFORMAT {\r\n        int32 dwSize; // offset: 19\r\n        int32 dwFlags;\r\n        char[4] dwFourCC;\r\n        int32 dwRGBBitCount;\r\n        int32 dwRBitMask;\r\n        int32 dwGBitMask;\r\n        int32 dwBBitMask;\r\n        int32 dwABitMask; // offset: 26\r\n    };\r\n    \r\n    DDS_HEADER {\r\n        int32 dwSize; // 1\r\n        int32 dwFlags;\r\n        int32 dwHeight;\r\n        int32 dwWidth;\r\n        int32 dwPitchOrLinearSize;\r\n        int32 dwDepth;\r\n        int32 dwMipMapCount; // offset: 7\r\n        int32[11] dwReserved1;\r\n        DDS_PIXELFORMAT ddspf; // offset 19\r\n        int32 dwCaps; // offset: 27\r\n        int32 dwCaps2;\r\n        int32 dwCaps3;\r\n        int32 dwCaps4;\r\n        int32 dwReserved2; // offset 31\r\n    };*/\r\n\r\n    /**\r\n     * Transcodes DXT into RGB565.\r\n     * Optimizations:\r\n     * 1. Use integer math to compute c2 and c3 instead of floating point\r\n     *    math.  Specifically:\r\n     *      c2 = 5/8 * c0 + 3/8 * c1\r\n     *      c3 = 3/8 * c0 + 5/8 * c1\r\n     *    This is about a 40% performance improvement.  It also appears to\r\n     *    match what hardware DXT decoders do, as the colors produced\r\n     *    by this integer math match what hardware produces, while the\r\n     *    floating point in dxtToRgb565Unoptimized() produce slightly\r\n     *    different colors (for one GPU this was tested on).\r\n     * 2. Unroll the inner loop.  Another ~10% improvement.\r\n     * 3. Compute r0, g0, b0, r1, g1, b1 only once instead of twice.\r\n     *    Another 10% improvement.\r\n     * 4. Use a Uint16Array instead of a Uint8Array.  Another 10% improvement.\r\n     * @author Evan Parker\r\n     * @param {Uint16Array} src The src DXT bits as a Uint16Array.\r\n     * @param {number} srcByteOffset\r\n     * @param {number} width\r\n     * @param {number} height\r\n     * @return {Uint16Array} dst\r\n     */\r\n    function dxtToRgb565(src, src16Offset, width, height) {\r\n        var c = new Uint16Array(4);\r\n        var dst = new Uint16Array(width * height);\r\n        var nWords = (width * height) / 4;\r\n        var m = 0;\r\n        var dstI = 0;\r\n        var i = 0;\r\n        var r0 = 0, g0 = 0, b0 = 0, r1 = 0, g1 = 0, b1 = 0;\r\n    \r\n        var blockWidth = width / 4;\r\n        var blockHeight = height / 4;\r\n        for (var blockY = 0; blockY < blockHeight; blockY++) {\r\n            for (var blockX = 0; blockX < blockWidth; blockX++) {\r\n                i = src16Offset + 4 * (blockY * blockWidth + blockX);\r\n                c[0] = src[i];\r\n                c[1] = src[i + 1];\r\n                r0 = c[0] & 0x1f;\r\n                g0 = c[0] & 0x7e0;\r\n                b0 = c[0] & 0xf800;\r\n                r1 = c[1] & 0x1f;\r\n                g1 = c[1] & 0x7e0;\r\n                b1 = c[1] & 0xf800;\r\n                // Interpolate between c0 and c1 to get c2 and c3.\r\n                // Note that we approximate 1/3 as 3/8 and 2/3 as 5/8 for\r\n                // speed.  This also appears to be what the hardware DXT\r\n                // decoder in many GPUs does :)\r\n                c[2] = ((5 * r0 + 3 * r1) >> 3)\r\n                    | (((5 * g0 + 3 * g1) >> 3) & 0x7e0)\r\n                    | (((5 * b0 + 3 * b1) >> 3) & 0xf800);\r\n                c[3] = ((5 * r1 + 3 * r0) >> 3)\r\n                    | (((5 * g1 + 3 * g0) >> 3) & 0x7e0)\r\n                    | (((5 * b1 + 3 * b0) >> 3) & 0xf800);\r\n                m = src[i + 2];\r\n                dstI = (blockY * 4) * width + blockX * 4;\r\n                dst[dstI] = c[m & 0x3];\r\n                dst[dstI + 1] = c[(m >> 2) & 0x3];\r\n                dst[dstI + 2] = c[(m >> 4) & 0x3];\r\n                dst[dstI + 3] = c[(m >> 6) & 0x3];\r\n                dstI += width;\r\n                dst[dstI] = c[(m >> 8) & 0x3];\r\n                dst[dstI + 1] = c[(m >> 10) & 0x3];\r\n                dst[dstI + 2] = c[(m >> 12) & 0x3];\r\n                dst[dstI + 3] = c[(m >> 14)];\r\n                m = src[i + 3];\r\n                dstI += width;\r\n                dst[dstI] = c[m & 0x3];\r\n                dst[dstI + 1] = c[(m >> 2) & 0x3];\r\n                dst[dstI + 2] = c[(m >> 4) & 0x3];\r\n                dst[dstI + 3] = c[(m >> 6) & 0x3];\r\n                dstI += width;\r\n                dst[dstI] = c[(m >> 8) & 0x3];\r\n                dst[dstI + 1] = c[(m >> 10) & 0x3];\r\n                dst[dstI + 2] = c[(m >> 12) & 0x3];\r\n                dst[dstI + 3] = c[(m >> 14)];\r\n            }\r\n        }\r\n        return dst;\r\n    }\r\n\r\n    function BGRtoRGB( byteArray )\r\n\t{\r\n\t\tfor(var j = 0, l = byteArray.length, tmp = 0; j < l; j+=4) //BGR fix\r\n\t\t{\r\n\t\t\ttmp = byteArray[j];\r\n\t\t\tbyteArray[j] = byteArray[j+2];\r\n\t\t\tbyteArray[j+2] = tmp;\r\n\t\t}\r\n\t}\r\n\r\n    function flipDXT( width, blockBytes, byteArray )\r\n\t{\r\n\t\t//TODO\r\n\t\t//var row = Uint8Array(width);\r\n\t}\r\n\r\n\r\n    /**\r\n     * Parses a DDS file from the given arrayBuffer and uploads it into the currently bound texture\r\n     *\r\n     * @param {WebGLRenderingContext} gl WebGL rendering context\r\n     * @param {WebGLCompressedTextureS3TC} ext WEBGL_compressed_texture_s3tc extension object\r\n     * @param {TypedArray} arrayBuffer Array Buffer containing the DDS files data\r\n     * @param {boolean} [loadMipmaps] If false only the top mipmap level will be loaded, otherwise all available mipmaps will be uploaded\r\n     *\r\n     * @returns {number} Number of mipmaps uploaded, 0 if there was an error\r\n     */\r\n    function uploadDDSLevels(gl, ext, arrayBuffer, loadMipmaps) {\r\n        var header = new Int32Array(arrayBuffer, 0, headerLengthInt),\r\n            fourCC, blockBytes, internalFormat,\r\n            width, height, dataLength, dataOffset, is_cubemap,\r\n            rgb565Data, byteArray, mipmapCount, i, face;\r\n\r\n        if(header[off_magic] != DDS_MAGIC) {\r\n            console.error(\"Invalid magic number in DDS header\");\r\n            return 0;\r\n        }\r\n        \r\n        if(!header[off_pfFlags] & DDPF_FOURCC) {\r\n            console.error(\"Unsupported format, must contain a FourCC code\");\r\n            return 0;\r\n        }\r\n\r\n        fourCC = header[off_pfFourCC];\r\n        switch(fourCC) {\r\n            case FOURCC_DXT1:\r\n                blockBytes = 8;\r\n                internalFormat = ext ? ext.COMPRESSED_RGB_S3TC_DXT1_EXT : null;\r\n                break;\r\n\r\n\t\t\t/*\r\n            case FOURCC_DXT1:\r\n                blockBytes = 8;\r\n                internalFormat = ext ? ext.COMPRESSED_RGBA_S3TC_DXT1_EXT : null;\r\n                break;\r\n\t\t\t*/\r\n\r\n            case FOURCC_DXT3:\r\n                blockBytes = 16;\r\n                internalFormat = ext ? ext.COMPRESSED_RGBA_S3TC_DXT3_EXT : null;\r\n                break;\r\n\r\n            case FOURCC_DXT5:\r\n                blockBytes = 16;\r\n                internalFormat = ext ? ext.COMPRESSED_RGBA_S3TC_DXT5_EXT : null;\r\n                break;\r\n\r\n            default:\r\n\t\t\t\tblockBytes = 4;\r\n\t\t\t\tfourCC = null;\r\n\t\t\t\tinternalFormat = gl.RGBA;\r\n                //console.error(\"Unsupported FourCC code:\", int32ToFourCC(fourCC), fourCC);\r\n                //return null;\r\n        }\r\n\r\n        mipmapCount = 1;\r\n        if(header[off_flags] & DDSD_MIPMAPCOUNT && loadMipmaps !== false) {\r\n            mipmapCount = Math.max(1, header[off_mipmapCount]);\r\n        }\r\n\r\n        width = header[off_width];\r\n        height = header[off_height];\r\n        dataOffset = header[off_size] + 4;\r\n\t\tis_cubemap = !!(header[off_caps+1] & DDSCAPS2_CUBEMAP);\r\n\r\n\t\tif(is_cubemap)\r\n\t\t{\r\n\t\t\t//console.error(\"Cubemaps not supported in DDS\");\r\n\t\t\t//return null;\r\n\r\n\t\t\tfor(face = 0; face < 6; ++face)\r\n\t\t\t{\r\n\t\t\t\twidth = header[off_width];\r\n\t\t\t\theight = header[off_height];\r\n\t\t\t\tfor(var i = 0; i < mipmapCount; ++i) {\r\n\t\t\t\t\tif(fourCC)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes;\r\n\t\t\t\t\t\tbyteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength);\r\n\t\t\t\t\t\tflipDXT( width, blockBytes, byteArray );\r\n\t\t\t\t\t\tgl.compressedTexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, i, internalFormat, width, height, 0, byteArray);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tgl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false );\r\n\t\t\t\t\t\tdataLength = width * height * blockBytes;\r\n\t\t\t\t\t\tbyteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength);\r\n\t\t\t\t\t\tBGRtoRGB(byteArray);\r\n\t\t\t\t\t\tgl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, i, internalFormat, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, byteArray);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tdataOffset += dataLength;\r\n\t\t\t\t\twidth *= 0.5;\r\n\t\t\t\t\theight *= 0.5;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse //2d texture\r\n\t\t{\r\n\t\t\tif(ext) {\r\n\t\t\t\tgl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true );\r\n\t\t\t\tfor(var i = 0; i < mipmapCount; ++i) {\r\n\t\t\t\t\tif(fourCC)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes;\r\n\t\t\t\t\t\tbyteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength);\r\n\t\t\t\t\t\tgl.compressedTexImage2D(gl.TEXTURE_2D, i, internalFormat, width, height, 0, byteArray);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdataLength = width * height * blockBytes;\r\n\t\t\t\t\t\tbyteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength);\r\n\t\t\t\t\t\tBGRtoRGB(byteArray);\r\n\t\t\t\t\t\tgl.texImage2D(gl.TEXTURE_2D, i, internalFormat, width, height, 0, internalFormat, gl.UNSIGNED_BYTE, byteArray);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tdataOffset += dataLength;\r\n\t\t\t\t\twidth *= 0.5;\r\n\t\t\t\t\theight *= 0.5;\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tif(fourCC == FOURCC_DXT1) {\r\n\t\t\t\t\tdataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes;\r\n\t\t\t\t\tbyteArray = new Uint16Array(arrayBuffer);\r\n\t\t\t\t\t//Decompress\r\n\t\t\t\t\trgb565Data = dxtToRgb565(byteArray, dataOffset / 2, width, height);\r\n\t\t\t\t\tgl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, width, height, 0, gl.RGB, gl.UNSIGNED_SHORT_5_6_5, rgb565Data);\r\n\t\t\t\t\tif(loadMipmaps) {\r\n\t\t\t\t\t\tgl.generateMipmap(gl.TEXTURE_2D);\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconsole.error(\"No manual decoder for\", int32ToFourCC(fourCC), \"and no native support\");\r\n\t\t\t\t\treturn 0;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n        return mipmapCount;\r\n    }\r\n\r\n    /**\r\n     * Parses a DDS file from the given arrayBuffer and uploads it into the currently bound texture\r\n     *\r\n     * @param {WebGLRenderingContext} gl WebGL rendering context\r\n     * @param {WebGLCompressedTextureS3TC} ext WEBGL_compressed_texture_s3tc extension object\r\n     * @param {TypedArray} arrayBuffer Array Buffer containing the DDS files data\r\n     * @param {boolean} [loadMipmaps] If false only the top mipmap level will be loaded, otherwise all available mipmaps will be uploaded\r\n     *\r\n     * @returns {number} Number of mipmaps uploaded, 0 if there was an error\r\n     */\r\n    function getDDSLevels( arrayBuffer, compressed_not_supported )\r\n\t{\r\n        var header = new Int32Array(arrayBuffer, 0, headerLengthInt),\r\n            fourCC, blockBytes, internalFormat,\r\n            width, height, dataLength, dataOffset, is_cubemap,\r\n            rgb565Data, byteArray, mipmapCount, i, face;\r\n\r\n        if(header[off_magic] != DDS_MAGIC) {\r\n            console.error(\"Invalid magic number in DDS header\");\r\n            return 0;\r\n        }\r\n        \r\n        if(!header[off_pfFlags] & DDPF_FOURCC) {\r\n            console.error(\"Unsupported format, must contain a FourCC code\");\r\n            return 0;\r\n        }\r\n\r\n        fourCC = header[off_pfFourCC];\r\n        switch(fourCC) {\r\n            case FOURCC_DXT1:\r\n                blockBytes = 8;\r\n                internalFormat = \"COMPRESSED_RGB_S3TC_DXT1_EXT\";\r\n                break;\r\n\r\n            case FOURCC_DXT3:\r\n                blockBytes = 16;\r\n                internalFormat = \"COMPRESSED_RGBA_S3TC_DXT3_EXT\";\r\n                break;\r\n\r\n            case FOURCC_DXT5:\r\n                blockBytes = 16;\r\n                internalFormat = \"COMPRESSED_RGBA_S3TC_DXT5_EXT\";\r\n                break;\r\n\r\n            default:\r\n\t\t\t\tblockBytes = 4;\r\n\t\t\t\tinternalFormat = \"RGBA\";\r\n                //console.error(\"Unsupported FourCC code:\", int32ToFourCC(fourCC), fourCC);\r\n                //return null;\r\n        }\r\n\r\n        mipmapCount = 1;\r\n        if(header[off_flags] & DDSD_MIPMAPCOUNT && loadMipmaps !== false) {\r\n            mipmapCount = Math.max(1, header[off_mipmapCount]);\r\n        }\r\n\r\n        width = header[off_width];\r\n        height = header[off_height];\r\n        dataOffset = header[off_size] + 4;\r\n\t\tis_cubemap = !!(header[off_caps+1] & DDSCAPS2_CUBEMAP);\r\n\r\n\t\tvar buffers = [];\r\n\r\n\t\tif(is_cubemap)\r\n\t\t{\r\n\t\t\tfor(var face = 0; face < 6; ++face)\r\n\t\t\t{\r\n\t\t\t\twidth = header[off_width];\r\n\t\t\t\theight = header[off_height];\r\n\t\t\t\tfor(var i = 0; i < mipmapCount; ++i)\r\n\t\t\t\t{\r\n\t\t\t\t\tif(fourCC)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes;\r\n\t\t\t\t\t\tbyteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength);\r\n\t\t\t\t\t\tbuffers.push({ tex: \"TEXTURE_CUBE_MAP\", face: face, mipmap: i, internalFormat: internalFormat, width: width, height: height, offset: 0, dataOffset: dataOffset, dataLength: dataLength });\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdataLength = width * height * blockBytes;\r\n\t\t\t\t\t\tbyteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength);\r\n\t\t\t\t\t\tBGRtoRGB(byteArray);\r\n\t\t\t\t\t\tbuffers.push({ tex: \"TEXTURE_CUBE_MAP\", face: face, mipmap: i, internalFormat: internalFormat, width: width, height: height, offset: 0, type: \"UNSIGNED_BYTE\", dataOffset: dataOffset, dataLength: dataLength });\r\n\t\t\t\t\t}\r\n\t\t\t\t\tdataOffset += dataLength;\r\n\t\t\t\t\twidth *= 0.5;\r\n\t\t\t\t\theight *= 0.5;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse //2d texture\r\n\t\t{\r\n\t\t\tif(!compressed_not_supported)\r\n\t\t\t{\r\n\t\t\t\tfor(var i = 0; i < mipmapCount; ++i) {\r\n\t\t\t\t\tdataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes;\r\n\t\t\t\t\tbyteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength);\r\n\t\t\t\t\t//gl.compressedTexImage2D(gl.TEXTURE_2D, i, internalFormat, width, height, 0, byteArray);\r\n\t\t\t\t\tbuffers.push({ tex: \"TEXTURE_2D\", mipmap: i, internalFormat: internalFormat, width: width, height: height, offset: 0, type: \"UNSIGNED_BYTE\", dataOffset: dataOffset, dataLength: dataLength });\r\n\t\t\t\t\tdataOffset += dataLength;\r\n\t\t\t\t\twidth *= 0.5;\r\n\t\t\t\t\theight *= 0.5;\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tif(fourCC == FOURCC_DXT1)\r\n\t\t\t\t{\r\n\t\t\t\t\tdataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes;\r\n\t\t\t\t\tbyteArray = new Uint16Array(arrayBuffer);\r\n\t\t\t\t\trgb565Data = dxtToRgb565(byteArray, dataOffset / 2, width, height);\r\n\t\t\t\t\t//gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, width, height, 0, gl.RGB, gl.UNSIGNED_SHORT_5_6_5, rgb565Data);\r\n\t\t\t\t\tbuffers.push({ tex: \"TEXTURE_2D\", mipmap: 0, internalFormat: \"RGB\", width: width, height: height, offset: 0, format:\"RGB\", type: \"UNSIGNED_SHORT_5_6_5\", data: rgb565Data });\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconsole.error(\"No manual decoder for\", int32ToFourCC(fourCC), \"and no native support\");\r\n\t\t\t\t\treturn 0;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n        return buffers;\r\n    }\r\n\r\n    /**\r\n     * Creates a texture from the DDS file at the given URL. Simple shortcut for the most common use case\r\n     *\r\n     * @param {WebGLRenderingContext} gl WebGL rendering context\r\n     * @param {WebGLCompressedTextureS3TC} ext WEBGL_compressed_texture_s3tc extension object\r\n     * @param {string} src URL to DDS file to be loaded\r\n     * @param {function} [callback] callback to be fired when the texture has finished loading\r\n     *\r\n     * @returns {WebGLTexture} New texture that will receive the DDS image data\r\n     */\r\n    function loadDDSTextureEx(gl, ext, src, texture, loadMipmaps, callback) {\r\n        var xhr = new XMLHttpRequest();\r\n        \r\n        xhr.open('GET', src, true);\r\n        xhr.responseType = \"arraybuffer\";\r\n        xhr.onload = function() {\r\n            if(this.status == 200) {\r\n\t\t\t\tvar header = new Int32Array(this.response, 0, headerLengthInt)\r\n\t\t\t\tvar is_cubemap = !!(header[off_caps+1] & DDSCAPS2_CUBEMAP);\r\n\t\t\t\tvar tex_type = is_cubemap ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;\r\n                gl.bindTexture(tex_type, texture);\r\n                var mipmaps = uploadDDSLevels(gl, ext, this.response, loadMipmaps);\r\n                gl.texParameteri(tex_type, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\r\n                gl.texParameteri(tex_type, gl.TEXTURE_MIN_FILTER, mipmaps > 1 ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);\r\n                gl.bindTexture(tex_type, null);\r\n\t\t\t\ttexture.texture_type = tex_type;\r\n\t\t\t\ttexture.width = header[off_width];\r\n\t\t\t\ttexture.height = header[off_height];\r\n            }\r\n\r\n            if(callback) {\r\n                callback(texture);\r\n            }\r\n        };\r\n        xhr.send(null);\r\n\r\n        return texture;\r\n    }\r\n\r\n    /**\r\n     * Creates a texture from the DDS file at the given ArrayBuffer.\r\n     *\r\n     * @param {WebGLRenderingContext} gl WebGL rendering context\r\n     * @param {WebGLCompressedTextureS3TC} ext WEBGL_compressed_texture_s3tc extension object\r\n     * @param {ArrayBuffer} data containing the DDS file\r\n     * @param {Texture} texture from GL.Texture\r\n     * @returns {WebGLTexture} New texture that will receive the DDS image data\r\n     */\r\n    function loadDDSTextureFromMemoryEx(gl, ext, data, texture, loadMipmaps) {\r\n\t\tvar header = new Int32Array(data, 0, headerLengthInt)\r\n\t\tvar is_cubemap = !!(header[off_caps+1] & DDSCAPS2_CUBEMAP);\r\n\t\tvar tex_type = is_cubemap ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;\r\n\r\n\t\tvar handler = texture.handler || texture;\r\n\r\n\t\tgl.bindTexture(tex_type, texture.handler);\r\n\r\n\t\t//upload data\r\n\t\tvar mipmaps = uploadDDSLevels(gl, ext, data, loadMipmaps);\r\n\r\n\t\tgl.texParameteri(tex_type, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\r\n\t\tgl.texParameteri(tex_type, gl.TEXTURE_MIN_FILTER, mipmaps > 1 ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);\r\n        if(is_cubemap)\r\n        {\r\n            gl.texParameteri(tex_type, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );\r\n            gl.texParameteri(tex_type, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );\r\n        }\r\n\r\n\t\tgl.bindTexture(tex_type, null); //unbind\r\n\t\tif(texture.handler)\r\n\t\t{\r\n\t\t\ttexture.texture_type = tex_type;\r\n\t\t\ttexture.width = header[off_width];\r\n\t\t\ttexture.height = header[off_height];\r\n\t\t}\r\n        return texture;\r\n    }\r\n\r\n    /**\r\n     * Extracts the texture info from a DDS file at the given ArrayBuffer.\r\n     *\r\n     * @param {ArrayBuffer} data containing the DDS file\r\n     *\r\n     * @returns {Object} contains mipmaps and properties\r\n     */\r\n    function getDDSTextureFromMemoryEx(data) {\r\n\t\tvar header = new Int32Array(data, 0, headerLengthInt)\r\n\t\tvar is_cubemap = !!(header[off_caps+1] & DDSCAPS2_CUBEMAP);\r\n\t\tvar tex_type = is_cubemap ? \"TEXTURE_CUBE_MAP\" : \"TEXTURE_2D\";\r\n\t\tvar buffers = getDDSLevels(data);\r\n\r\n\t\tvar texture = {\r\n\t\t\ttype: tex_type,\r\n\t\t\tbuffers: buffers,\r\n\t\t\tdata: data,\r\n\t\t\twidth: header[off_width],\r\n\t\t\theight: header[off_height]\r\n\t\t};\r\n\r\n        return texture;\r\n    }\r\n\r\n    /**\r\n     * Creates a texture from the DDS file at the given URL. Simple shortcut for the most common use case\r\n     *\r\n     * @param {WebGLRenderingContext} gl WebGL rendering context\r\n     * @param {WebGLCompressedTextureS3TC} ext WEBGL_compressed_texture_s3tc extension object\r\n     * @param {string} src URL to DDS file to be loaded\r\n     * @param {function} [callback] callback to be fired when the texture has finished loading\r\n     *\r\n     * @returns {WebGLTexture} New texture that will receive the DDS image data\r\n     */\r\n    function loadDDSTexture(gl, ext, src, callback) {\r\n        var texture = gl.createTexture();\r\n        var ext = gl.getExtension(\"WEBGL_compressed_texture_s3tc\");\r\n        loadDDSTextureEx(gl, ext, src, texture, true, callback);\r\n        return texture;\r\n    }\r\n\r\n    return {\r\n        dxtToRgb565: dxtToRgb565,\r\n        uploadDDSLevels: uploadDDSLevels,\r\n        loadDDSTextureEx: loadDDSTextureEx,\r\n        loadDDSTexture: loadDDSTexture,\r\n\t\tloadDDSTextureFromMemoryEx: loadDDSTextureFromMemoryEx,\r\n\t\tgetDDSTextureFromMemoryEx: getDDSTextureFromMemoryEx\r\n    };\r\n\r\n})();\r\n\r\nif(typeof(global) != \"undefined\")\r\n\tglobal.DDS = DDS;\r\n\n/* this file adds some extra functions to gl-matrix library */\r\nif(typeof(glMatrix) == \"undefined\")\r\n\tthrow(\"You must include glMatrix on your project\");\r\n\r\nMath.clamp = function(v,a,b) { return (a > v ? a : (b < v ? b : v)); }\r\n\r\nvar V3 = vec3.create;\r\nvar M4 = vec3.create;\r\n\r\n\r\nvec3.ZERO = vec3.fromValues(0,0,0);\r\nvec3.FRONT = vec3.fromValues(0,0,-1);\r\nvec3.UP = vec3.fromValues(0,1,0);\r\nvec3.RIGHT = vec3.fromValues(1,0,0);\r\n\r\nvec2.rotate = function(out,vec,angle_in_rad)\r\n{\r\n\tvar x = vec[0], y = vec[1];\r\n\tvar cos = Math.cos(angle_in_rad);\r\n\tvar sin = Math.sin(angle_in_rad);\r\n\tout[0] = x * cos - y * sin;\r\n\tout[1] = x * sin + y * cos;\r\n\treturn out;\r\n}\r\n\r\nvec3.zero = function(a)\r\n{\r\n\ta[0] = a[1] = 0.0;\r\n\treturn a;\r\n}\r\n\r\n//for signed angles\r\nvec2.perpdot = function(a,b)\r\n{\r\n\treturn a[1] * b[0] + -a[0] * b[1];\r\n}\r\n\r\nvec2.computeSignedAngle = function( a, b )\r\n{\r\n\treturn Math.atan2( vec2.perpdot(a,b), vec2.dot(a,b) );\r\n}\r\n\r\nvec2.random = function( vec, scale )\r\n{\r\n\tscale = scale || 1.0;\r\n\tvec[0] = Math.random() * scale;\r\n\tvec[1] = Math.random() * scale;\r\n\treturn vec;\r\n}\r\n\r\nvec3.zero = function(a)\r\n{\r\n\ta[0] = a[1] = a[2] = 0.0;\r\n\treturn a;\r\n}\r\n\r\nvec3.minValue = function(a)\r\n{\r\n\tif(a[0] < a[1] && a[0] < a[2]) return a[0];\r\n\tif(a[1] < a[2]) return a[1];\r\n\treturn a[2];\r\n}\r\n\r\nvec3.maxValue = function(a)\r\n{\r\n\tif(a[0] > a[1] && a[0] > a[2]) return a[0];\r\n\tif(a[1] > a[2]) return a[1];\r\n\treturn a[2];\r\n}\r\n\r\nvec3.minValue = function(a)\r\n{\r\n\tif(a[0] < a[1] && a[0] < a[2]) return a[0];\r\n\tif(a[1] < a[2]) return a[1];\r\n\treturn a[2];\r\n}\r\n\r\nvec3.addValue = function(out,a,v)\r\n{\r\n\tout[0] = a[0] + v;\r\n\tout[1] = a[1] + v;\r\n\tout[2] = a[2] + v;\r\n}\r\n\r\nvec3.subValue = function(out,a,v)\r\n{\r\n\tout[0] = a[0] - v;\r\n\tout[1] = a[1] - v;\r\n\tout[2] = a[2] - v;\r\n}\r\n\r\nvec3.toArray = function(vec)\r\n{\r\n\treturn [vec[0],vec[1],vec[2]];\r\n}\r\n\r\nvec3.rotateX = function(out,vec,angle_in_rad)\r\n{\r\n\tvar y = vec[1], z = vec[2];\r\n\tvar cos = Math.cos(angle_in_rad);\r\n\tvar sin = Math.sin(angle_in_rad);\r\n\r\n\tout[0] = vec[0];\r\n\tout[1] = y * cos - z * sin;\r\n\tout[2] = y * sin + z * cos;\r\n\treturn out;\r\n}\r\n\r\nvec3.rotateY = function(out,vec,angle_in_rad)\r\n{\r\n\tvar x = vec[0], z = vec[2];\r\n\tvar cos = Math.cos(angle_in_rad);\r\n\tvar sin = Math.sin(angle_in_rad);\r\n\r\n\tout[0] = x * cos - z * sin;\r\n\tout[1] = vec[1];\r\n\tout[2] = x * sin + z * cos;\r\n\treturn out;\r\n}\r\n\r\nvec3.rotateZ = function(out,vec,angle_in_rad)\r\n{\r\n\tvar x = vec[0], y = vec[1];\r\n\tvar cos = Math.cos(angle_in_rad);\r\n\tvar sin = Math.sin(angle_in_rad);\r\n\r\n\tout[0] = x * cos - y * sin;\r\n\tout[1] = x * sin + y * cos;\r\n\tout[2] = vec[2];\r\n\treturn out;\r\n}\r\n\r\nvec3.angle = function( a, b )\r\n{\r\n\treturn Math.acos( vec3.dot(a,b) );\r\n}\r\n\r\nvec3.signedAngle = function(from, to, axis)\r\n{\r\n\tvar unsignedAngle = vec3.angle( from, to );\r\n\tvar cross_x = from[1] * to[2] - from[2] * to[1];\r\n\tvar cross_y = from[2] * to[0] - from[0] * to[2];\r\n\tvar cross_z = from[0] * to[1] - from[1] * to[0];\r\n\tvar sign = Math.sign(axis[0] * cross_x + axis[1] * cross_y + axis[2] * cross_z);\r\n\treturn unsignedAngle * sign;\r\n}\r\n\r\nvec3.random = function(vec, scale)\r\n{\r\n\tscale = scale || 1.0;\r\n\tvec[0] = Math.random() * scale;\r\n\tvec[1] = Math.random() * scale;\r\n\tvec[2] = Math.random() * scale;\r\n\treturn vec;\r\n}\r\n\r\n//converts a polar coordinate (radius, lat, long) to (x,y,z)\r\nvec3.polarToCartesian = function(out, v)\r\n{\r\n\tvar r = v[0];\r\n\tvar lat = v[1];\r\n\tvar lon = v[2];\r\n\tout[0] = r * Math.cos(lat) * Math.sin(lon);\r\n\tout[1] = r * Math.sin(lat);\r\n\tout[2] = r * Math.cos(lat) * Math.cos(lon);\r\n\treturn out;\r\n}\r\n\r\nvec3.reflect = function(out, v, n)\r\n{\r\n\tvar x = v[0]; var y = v[1]; var z = v[2];\r\n\tvec3.scale( out, n, -2 * vec3.dot(v,n) );\r\n\tout[0] += x;\r\n\tout[1] += y;\r\n\tout[2] += z;\r\n\treturn out;\r\n}\r\n\r\n/* VEC4 */\r\nvec4.random = function(vec, scale)\r\n{\r\n\tscale = scale || 1.0;\r\n\tvec[0] = Math.random() * scale;\r\n\tvec[1] = Math.random() * scale;\r\n\tvec[2] = Math.random() * scale;\r\n\tvec[3] = Math.random() * scale;\t\r\n\treturn vec;\r\n}\r\n\r\nvec4.toArray = function(vec)\r\n{\r\n\treturn [vec[0],vec[1],vec[2],vec[3]];\r\n}\r\n\r\n\r\n/** MATRIX ********************/\r\nmat3.IDENTITY = mat3.create();\r\nmat4.IDENTITY = mat4.create();\r\n\r\nmat4.toArray = function(mat)\r\n{\r\n\treturn [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]];\r\n}\r\n\r\nmat4.setUpAndOrthonormalize = function(out, m, up)\r\n{\r\n\tif(m != out)\r\n\t\tmat4.copy(out,m);\r\n\tvar right = out.subarray(0,3);\r\n\tvec3.normalize(out.subarray(4,7),up);\r\n\tvar front = out.subarray(8,11);\r\n\tvec3.cross( right, up, front );\r\n\tvec3.normalize( right, right );\r\n\tvec3.cross( front, right, up );\r\n\tvec3.normalize( front, front );\r\n}\r\n\r\nmat4.multiplyVec3 = function(out, m, a) {\r\n    var x = a[0], y = a[1], z = a[2];\r\n    out[0] = m[0] * x + m[4] * y + m[8] * z + m[12];\r\n    out[1] = m[1] * x + m[5] * y + m[9] * z + m[13];\r\n    out[2] = m[2] * x + m[6] * y + m[10] * z + m[14];\r\n    return out;\r\n};\r\n\r\n//from https://github.com/hughsk/from-3d-to-2d/blob/master/index.js\r\n//m should be a projection matrix (or a VP or MVP)\r\n//projects vector from 3D to 2D and returns the value in normalized screen space\r\nmat4.projectVec3 = function(out, m, a)\r\n{\r\n\tvar ix = a[0];\r\n\tvar iy = a[1];\r\n\tvar iz = a[2];\r\n\r\n\tvar ox = m[0] * ix + m[4] * iy + m[8] * iz + m[12];\r\n\tvar oy = m[1] * ix + m[5] * iy + m[9] * iz + m[13];\r\n\tvar oz = m[2] * ix + m[6] * iy + m[10] * iz + m[14];\r\n\tvar ow = m[3] * ix + m[7] * iy + m[11] * iz + m[15];\r\n\r\n\tout[0] = (ox / ow + 1) / 2;\r\n\tout[1] = (oy / ow + 1) / 2;\r\n\tout[2] = (oz / ow + 1) / 2;\r\n\treturn out;\r\n};\r\n\r\n\r\n//from https://github.com/hughsk/from-3d-to-2d/blob/master/index.js\r\nvec3.project = function(out, vec,  mvp, viewport) {\r\n\tviewport = viewport || gl.viewport_data;\r\n\r\n\tvar m = mvp;\r\n\r\n\tvar ix = vec[0];\r\n\tvar iy = vec[1];\r\n\tvar iz = vec[2];\r\n\r\n\tvar ox = m[0] * ix + m[4] * iy + m[8] * iz + m[12];\r\n\tvar oy = m[1] * ix + m[5] * iy + m[9] * iz + m[13];\r\n\tvar oz = m[2] * ix + m[6] * iy + m[10] * iz + m[14];\r\n\tvar ow = m[3] * ix + m[7] * iy + m[11] * iz + m[15];\r\n\r\n\tvar projx =     (ox / ow + 1) / 2;\r\n\tvar projy = 1 - (oy / ow + 1) / 2;\r\n\tvar projz =     (oz / ow + 1) / 2;\r\n\r\n\tout[0] = projx * viewport[2] + viewport[0];\r\n\tout[1] = projy * viewport[3] + viewport[1];\r\n\tout[2] = projz; //ow\r\n\treturn out;\r\n};\r\n\r\nvar unprojectMat = mat4.create();\r\nvar unprojectVec = vec4.create();\r\n\r\nvec3.unproject = function (out, vec, viewprojection, viewport) {\r\n\r\n\tvar m = unprojectMat;\r\n\tvar v = unprojectVec;\r\n\t\r\n\tv[0] = (vec[0] - viewport[0]) * 2.0 / viewport[2] - 1.0;\r\n\tv[1] = (vec[1] - viewport[1]) * 2.0 / viewport[3] - 1.0;\r\n\tv[2] = 2.0 * vec[2] - 1.0;\r\n\tv[3] = 1.0;\r\n\t\r\n\tif(!mat4.invert(m,viewprojection)) \r\n\t\treturn null;\r\n\t\r\n\tvec4.transformMat4(v, v, m);\r\n\tif(v[3] === 0.0) \r\n\t\treturn null;\r\n\r\n\tout[0] = v[0] / v[3];\r\n\tout[1] = v[1] / v[3];\r\n\tout[2] = v[2] / v[3];\r\n\t\r\n\treturn out;\r\n};\r\n\r\n//without translation\r\nmat4.rotateVec3 = function(out, m, a) {\r\n    var x = a[0], y = a[1], z = a[2];\r\n    out[0] = m[0] * x + m[4] * y + m[8] * z;\r\n    out[1] = m[1] * x + m[5] * y + m[9] * z;\r\n    out[2] = m[2] * x + m[6] * y + m[10] * z;\r\n    return out;\r\n};\r\n\r\nmat4.fromTranslationFrontTop = function (out, pos, front, top)\r\n{\r\n\tvec3.cross(out.subarray(0,3), front, top);\r\n\tout.set(top,4);\r\n\tout.set(front,8);\r\n\tout.set(pos,12);\r\n\treturn out;\r\n}\r\n\r\n\r\nmat4.translationMatrix = function (v)\r\n{\r\n\tvar out = mat4.create();\r\n\tout[12] = v[0];\r\n\tout[13] = v[1];\r\n\tout[14] = v[2];\r\n\treturn out;\r\n}\r\n\r\nmat4.setTranslation = function (out, v)\r\n{\r\n\tout[12] = v[0];\r\n\tout[13] = v[1];\r\n\tout[14] = v[2];\r\n\treturn out;\r\n}\r\n\r\n\r\nmat4.getTranslation = function (out, matrix)\r\n{\r\n\tout[0] = matrix[12];\r\n\tout[1] = matrix[13];\r\n\tout[2] = matrix[14];\r\n\treturn out;\r\n}\r\n\r\n//returns the matrix without rotation\r\nmat4.toRotationMat4 = function (out, mat) {\r\n\tmat4.copy(out,mat);\r\n\tout[12] = out[13] = out[14] = 0.0;\r\n\treturn out;\r\n};\r\n\r\nmat4.swapRows = function(out, mat, row, row2)\r\n{\r\n\tif(out != mat)\r\n\t{\r\n\t\tmat4.copy(out, mat);\r\n\t\tout[4*row] = mat[4*row2];\r\n\t\tout[4*row+1] = mat[4*row2+1];\r\n\t\tout[4*row+2] = mat[4*row2+2];\r\n\t\tout[4*row+3] = mat[4*row2+3];\r\n\t\tout[4*row2] = mat[4*row];\r\n\t\tout[4*row2+1] = mat[4*row+1];\r\n\t\tout[4*row2+2] = mat[4*row+2];\r\n\t\tout[4*row2+3] = mat[4*row+3];\r\n\t\treturn out;\r\n\t}\r\n\r\n\tvar temp = new Float32Array(matrix.subarray(row*4,row*5));\r\n\tmatrix.set( matrix.subarray(row2*4,row2*5), row*4 );\r\n\tmatrix.set( temp, row2*4 );\r\n\treturn out;\r\n}\r\n\r\n//used in skinning\r\nmat4.scaleAndAdd = function(out, mat, mat2, v)\r\n{\r\n\tout[0] = mat[0] + mat2[0] * v; \tout[1] = mat[1] + mat2[1] * v; \tout[2] = mat[2] + mat2[2] * v; \tout[3] = mat[3] + mat2[3] * v;\r\n\tout[4] = mat[4] + mat2[4] * v; \tout[5] = mat[5] + mat2[5] * v; \tout[6] = mat[6] + mat2[6] * v; \tout[7] = mat[7] + mat2[7] * v;\r\n\tout[8] = mat[8] + mat2[8] * v; \tout[9] = mat[9] + mat2[9] * v; \tout[10] = mat[10] + mat2[10] * v; \tout[11] = mat[11] + mat2[11] * v;\r\n\tout[12] = mat[12] + mat2[12] * v;  out[13] = mat[13] + mat2[13] * v; \tout[14] = mat[14] + mat2[14] * v; \tout[15] = mat[15] + mat2[15] * v;\r\n\treturn out;\r\n}\r\n\r\nquat.fromAxisAngle = function(axis, rad)\r\n{\r\n\tvar out = quat.create();\r\n    rad = rad * 0.5;\r\n    var s = Math.sin(rad);\r\n    out[0] = s * axis[0];\r\n    out[1] = s * axis[1];\r\n    out[2] = s * axis[2];\r\n    out[3] = Math.cos(rad);\r\n    return out;\r\n}\r\n\r\n//from https://answers.unity.com/questions/467614/what-is-the-source-code-of-quaternionlookrotation.html\r\nquat.lookRotation = (function(){\r\n\tvar vector = vec3.create();\r\n\tvar vector2 = vec3.create();\r\n\tvar vector3 = vec3.create();\r\n\r\n\treturn function( q, front, up )\r\n\t{\r\n\t\tvec3.normalize(vector,front);\r\n\t\tvec3.cross( vector2, up, vector );\r\n\t\tvec3.normalize(vector2,vector2);\r\n\t\tvec3.cross( vector3, vector, vector2 );\r\n\r\n\t\tvar m00 = vector2[0];\r\n\t\tvar m01 = vector2[1];\r\n\t\tvar m02 = vector2[2];\r\n\t\tvar m10 = vector3[0];\r\n\t\tvar m11 = vector3[1];\r\n\t\tvar m12 = vector3[2];\r\n\t\tvar m20 = vector[0];\r\n\t\tvar m21 = vector[1];\r\n\t\tvar m22 = vector[2];\r\n\r\n\t\tvar num8 = (m00 + m11) + m22;\r\n\r\n\t\t if (num8 > 0)\r\n\t\t {\r\n\t\t\t var num = Math.sqrt(num8 + 1);\r\n\t\t\t q[3] = num * 0.5;\r\n\t\t\t num = 0.5 / num;\r\n\t\t\t q[0] = (m12 - m21) * num;\r\n\t\t\t q[1] = (m20 - m02) * num;\r\n\t\t\t q[2] = (m01 - m10) * num;\r\n\t\t\t return q;\r\n\t\t }\r\n\t\t if ((m00 >= m11) && (m00 >= m22))\r\n\t\t {\r\n\t\t\t var num7 = Math.sqrt(((1 + m00) - m11) - m22);\r\n\t\t\t var num4 = 0.5 / num7;\r\n\t\t\t q[0] = 0.5 * num7;\r\n\t\t\t q[1] = (m01 + m10) * num4;\r\n\t\t\t q[2] = (m02 + m20) * num4;\r\n\t\t\t q[3] = (m12 - m21) * num4;\r\n\t\t\t return q;\r\n\t\t }\r\n\t\t if (m11 > m22)\r\n\t\t {\r\n\t\t\t var num6 = Math.sqrt(((1 + m11) - m00) - m22);\r\n\t\t\t var num3 = 0.5 / num6;\r\n\t\t\t q[0] = (m10+ m01) * num3;\r\n\t\t\t q[1] = 0.5 * num6;\r\n\t\t\t q[2] = (m21 + m12) * num3;\r\n\t\t\t q[3] = (m20 - m02) * num3;\r\n\t\t\t return q; \r\n\t\t }\r\n\t\t var num5 = Math.sqrt(((1 + m22) - m00) - m11);\r\n\t\t var num2 = 0.5 / num5;\r\n\t\t q[0] = (m20 + m02) * num2;\r\n\t\t q[1] = (m21 + m12) * num2;\r\n\t\t q[2] = 0.5 * num5;\r\n\t\t q[3] = (m01 - m10) * num2;\r\n\t\t return q;\r\n\t};\r\n})();\r\n\r\n/*\r\nquat.toEuler = function(out, quat) {\r\n\tvar q = quat;\r\n\tvar heading, attitude, bank;\r\n\r\n\tif( (q[0]*q[1] + q[2]*q[3]) == 0.5 )\r\n\t{\r\n\t\theading = 2 * Math.atan2(q[0],q[3]);\r\n\t\tbank = 0;\r\n\t\tattitude = 0; //?\r\n\t}\r\n\telse if( (q[0]*q[1] + q[2]*q[3]) == 0.5 )\r\n\t{\r\n\t\theading = -2 * Math.atan2(q[0],q[3]);\r\n\t\tbank = 0;\r\n\t\tattitude = 0; //?\r\n\t}\r\n\telse\r\n\t{\r\n\t\theading = Math.atan2( 2*(q[1]*q[3] - q[0]*q[2]) , 1 - 2 * (q[1]*q[1] - q[2]*q[2]) );\r\n\t\tattitude = Math.asin( 2*(q[0]*q[1] - q[2]*q[3]) );\r\n\t\tbank = Math.atan2( 2*(q[0]*q[3] - q[1]*q[2]), 1 - 2*(q[0]*q[0] - q[2]*q[2]) );\r\n\t}\r\n\r\n\tif(!out)\r\n\t\tout = vec3.create();\r\n\tvec3.set(out, heading, attitude, bank);\r\n\treturn out;\r\n}\r\n*/\r\n\r\n/*\r\n//FROM https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles\r\n//doesnt work well\r\nquat.toEuler = function(out, q)\r\n{\r\n    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]);\r\n    var pitch = Math.asin(2*q[0]*q[2] - 2*q[3]*q[1]);\r\n    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]);\r\n\tif(!out)\r\n\t\tout = vec3.create();\r\n\tvec3.set(out, yaw, pitch, roll);\r\n\treturn out;\r\n}\r\n\r\nquat.fromEuler = function(out, vec) {\r\n\tvar yaw = vec[0];\r\n\tvar pitch = vec[1];\r\n\tvar roll = vec[2];\r\n\r\n\tvar C1 = Math.cos(yaw*0.5);\r\n\tvar C2 = Math.cos(pitch*0.5);\r\n\tvar C3 = Math.cos(roll*0.5);\r\n\tvar S1 = Math.sin(yaw*0.5);\r\n\tvar S2 = Math.sin(pitch*0.5);\r\n\tvar S3 = Math.sin(roll*0.5);\r\n\r\n\tvar x = C1*C2*C3 + S1*S2*S3;\r\n\tvar y = S1*C2*C3 - C1*S2*S3;\r\n\tvar z = C1*S2*C3 + S1*C2*S3;\r\n\tvar w = C1*C2*S3 - S1*S2*C3;\r\n\r\n\tquat.set(out, x,y,z,w );\r\n\tquat.normalize(out,out); //necessary?\r\n\treturn out;\r\n}\r\n*/\r\n\r\nquat.toEuler = function(out, q)\r\n{\r\n    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]);\r\n    var attitude = Math.asin(2*q[0]*q[1] + 2*q[2]*q[3]);\r\n    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]);\r\n\tif(!out)\r\n\t\tout = vec3.create();\r\n\tvec3.set(out, heading, attitude, bank);\r\n\treturn out;\r\n}\r\n\r\nquat.fromEuler = function(out, vec) {\r\n\tvar heading = vec[0];\r\n\tvar attitude = vec[1];\r\n\tvar bank = vec[2];\r\n\r\n\tvar C1 = Math.cos(heading); //yaw\r\n\tvar C2 = Math.cos(attitude); //pitch\r\n\tvar C3 = Math.cos(bank); //roll\r\n\tvar S1 = Math.sin(heading);\r\n\tvar S2 = Math.sin(attitude);\r\n\tvar S3 = Math.sin(bank);\r\n\r\n\tvar w = Math.sqrt(1.0 + C1 * C2 + C1*C3 - S1 * S2 * S3 + C2*C3) * 0.5;\r\n\tif(w == 0.0)\r\n\t{\r\n\t\tw = 0.000001;\r\n\t\t//quat.set(out, 0,0,0,1 );\r\n\t\t//return out;\r\n\t}\r\n\r\n\tvar x = (C2 * S3 + C1 * S3 + S1 * S2 * C3) / (4.0 * w);\r\n\tvar y = (S1 * C2 + S1 * C3 + C1 * S2 * S3) / (4.0 * w);\r\n\tvar z = (-S1 * S3 + C1 * S2 * C3 + S2) /(4.0 * w);\r\n\tquat.set(out, x,y,z,w );\r\n\tquat.normalize(out,out);\r\n\treturn out;\r\n};\r\n\r\n\r\n//not tested\r\nquat.fromMat4 = function(out,m)\r\n{\r\n\tvar trace = m[0] + m[5] + m[10];\r\n\tif ( trace > 0.0 )\r\n\t{\r\n\t\tvar s = Math.sqrt( trace + 1.0 );\r\n\t\tout[3] = s * 0.5;//w\r\n\t\tvar recip = 0.5 / s;\r\n\t\tout[0] = ( m[9] - m[6] ) * recip; //2,1  1,2\r\n\t\tout[1] = ( m[2] - m[8] ) * recip; //0,2  2,0\r\n\t\tout[2] = ( m[4] - m[1] ) * recip; //1,0  0,1\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar i = 0;\r\n\t\tif( m[5] > m[0] )\r\n\t\t i = 1;\r\n\t\tif( m[10] > m[i*4+i] )\r\n\t\t i = 2;\r\n\t\tvar j = ( i + 1 ) % 3;\r\n\t\tvar k = ( j + 1 ) % 3;\r\n\t\tvar s = Math.sqrt( m[i*4+i] - m[j*4+j] - m[k*4+k] + 1.0 );\r\n\t\tout[i] = 0.5 * s;\r\n\t\tvar recip = 0.5 / s;\r\n\t\tout[3] = ( m[k*4+j] - m[j*4+k] ) * recip;//w\r\n\t\tout[j] = ( m[j*4+i] + m[i*4+j] ) * recip;\r\n\t\tout[k] = ( m[k*4+i] + m[i*4+k] ) * recip;\r\n\t}\r\n\tquat.normalize(out,out);\r\n}\r\n\r\n//col according to common matrix notation, here are stored as rows\r\nvec3.getMat3Column = function(out, m, index )\r\n{\r\n\tout[0] = m[index*3];\r\n\tout[1] = m[index*3 + 1];\r\n\tout[2] = m[index*3 + 2];\r\n\treturn out;\r\n}\r\n\r\nmat3.setColumn = function(out, v, index )\r\n{\r\n\tout[index*3] = v[0];\r\n\tout[index*3+1] = v[1];\r\n\tout[index*3+2] = v[2];\r\n\treturn out;\r\n}\r\n\r\n\r\n//http://matthias-mueller-fischer.ch/publications/stablePolarDecomp.pdf\r\n//reusing the previous quaternion as an indicator to keep perpendicularity\r\nquat.fromMat3AndQuat = (function(){\r\n\tvar temp_mat3 = mat3.create();\r\n\tvar temp_quat = quat.create();\r\n\tvar Rcol0 = vec3.create();\r\n\tvar Rcol1 = vec3.create();\r\n\tvar Rcol2 = vec3.create();\r\n\tvar Acol0 = vec3.create();\r\n\tvar Acol1 = vec3.create();\r\n\tvar Acol2 = vec3.create();\r\n\tvar RAcross0 = vec3.create();\r\n\tvar RAcross1 = vec3.create();\r\n\tvar RAcross2 = vec3.create();\r\n\tvar omega = vec3.create();\r\n\tvar axis = mat3.create();\r\n\r\n\treturn function( q, A, max_iter )\r\n\t{\r\n\t\tmax_iter = max_iter || 25;\r\n\t\tfor (var iter = 0; iter < max_iter; ++iter)\r\n\t\t{\r\n\t\t\tvar R = mat3.fromQuat( temp_mat3, q );\r\n\t\t\tvec3.getMat3Column(Rcol0,R,0);\r\n\t\t\tvec3.getMat3Column(Rcol1,R,1);\r\n\t\t\tvec3.getMat3Column(Rcol2,R,2);\r\n\t\t\tvec3.getMat3Column(Acol0,A,0);\r\n\t\t\tvec3.getMat3Column(Acol1,A,1);\r\n\t\t\tvec3.getMat3Column(Acol2,A,2);\r\n\t\t\tvec3.cross( RAcross0, Rcol0, Acol0 );\r\n\t\t\tvec3.cross( RAcross1, Rcol1, Acol1 );\r\n\t\t\tvec3.cross( RAcross2, Rcol2, Acol2 );\r\n\t\t\tvec3.add( omega, RAcross0, RAcross1 );\r\n\t\t\tvec3.add( omega, omega, RAcross2 );\r\n\t\t\tvar d = 1.0 / Math.abs( vec3.dot(Rcol0,Acol0) + vec3.dot(Rcol1,Acol1) + vec3.dot(Rcol2,Acol2) ) + 1.0e-9;\r\n\t\t\tvec3.scale( omega, omega, d );\r\n\t\t\tvar w = vec3.length(omega);\r\n\t\t\tif (w < 1.0e-9)\r\n\t\t\t\tbreak;\r\n\t\t\tvec3.scale(omega,omega,1/w); //normalize\r\n\t\t\tquat.setAxisAngle( temp_quat, omega, w );\r\n\t\t\tquat.mul( q, temp_quat, q );\r\n\t\t\tquat.normalize(q,q);\r\n\t\t}\r\n\t\treturn q;\r\n\t};\r\n})();\r\n\r\n//http://number-none.com/product/IK%20with%20Quaternion%20Joint%20Limits/\r\nquat.rotateToFrom = (function(){ \r\n\tvar tmp = vec3.create();\r\n\treturn function(out, v1, v2)\r\n\t{\r\n\t\tout = out || quat.create();\r\n\t\tvar axis = vec3.cross(tmp, v1, v2);\r\n\t\tvar dot = vec3.dot(v1, v2);\r\n\t\tif( dot < -1 + 0.01){\r\n\t\t\tout[0] = 0;\r\n\t\t\tout[1] = 1; \r\n\t\t\tout[2] = 0; \r\n\t\t\tout[3] = 0; \r\n\t\t\treturn out;\r\n\t\t}\r\n\t\tout[0] = axis[0] * 0.5;\r\n\t\tout[1] = axis[1] * 0.5; \r\n\t\tout[2] = axis[2] * 0.5; \r\n\t\tout[3] = (1 + dot) * 0.5; \r\n\t\tquat.normalize(out, out); \r\n\t\treturn out;    \r\n\t}\r\n})();\r\n\r\nquat.lookAt = (function(){ \r\n\tvar axis = vec3.create();\r\n\t\r\n\treturn function( out, forwardVector, up )\r\n\t{\r\n\t\tvar dot = vec3.dot( vec3.FRONT, forwardVector );\r\n\r\n\t\tif ( Math.abs( dot - (-1.0)) < 0.000001 )\r\n\t\t{\r\n\t\t\tout.set( vec3.UP );\r\n\t\t\tout[3] = Math.PI;\r\n\t\t\treturn out;\r\n\t\t}\r\n\t\tif ( Math.abs(dot - 1.0) < 0.000001 )\r\n\t\t{\r\n\t\t\treturn quat.identity( out );\r\n\t\t}\r\n\r\n\t\tvar rotAngle = Math.acos( dot );\r\n\t\tvec3.cross( axis, vec3.FRONT, forwardVector );\r\n\t\tvec3.normalize( axis, axis );\r\n\t\tquat.setAxisAngle( out, axis, rotAngle );\r\n\t\treturn out;\r\n\t}\r\n})();\r\n\r\n\r\n\r\n\n/**\r\n* @namespace GL\r\n*/\r\n\r\n/**\r\n* Indexer used to reuse vertices among a mesh\r\n* @class Indexer\r\n* @constructor\r\n*/\r\nGL.Indexer = function Indexer() {\r\n  this.unique = [];\r\n  this.indices = [];\r\n  this.map = {};\r\n}\r\nGL.Indexer.prototype = {\r\n\tadd: function(obj) {\r\n    var key = JSON.stringify(obj);\r\n    if (!(key in this.map)) {\r\n      this.map[key] = this.unique.length;\r\n      this.unique.push(obj);\r\n    }\r\n    return this.map[key];\r\n  }\r\n};\r\n\r\n/**\r\n* A data buffer to be stored in the GPU\r\n* @class Buffer\r\n* @constructor\r\n* @param {Number} target gl.ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER\r\n* @param {ArrayBufferView} data the data in typed-array format\r\n* @param {number} spacing number of numbers per component (3 per vertex, 2 per uvs...), default 3\r\n* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW \r\n*/\r\nGL.Buffer = function Buffer( target, data, spacing, stream_type, gl ) {\r\n\tif(GL.debug)\r\n\t\tconsole.log(\"GL.Buffer created\");\r\n\r\n\tif(gl !== null)\r\n\t\tgl = gl || global.gl;\r\n\tthis.gl = gl;\r\n\r\n\tthis.buffer = null; //webgl buffer\r\n\tthis.target = target; //GL.ARRAY_BUFFER, GL.ELEMENT_ARRAY_BUFFER\r\n\tthis.attribute = null; //name of the attribute in the shader (\"a_vertex\",\"a_normal\",\"a_coord\",...)\r\n\r\n\t//optional\r\n\tthis.data = data;\r\n\tthis.spacing = spacing || 3;\r\n\r\n\tif(this.data && this.gl)\r\n\t\tthis.upload(stream_type);\r\n}\r\n\r\n/**\r\n* binds the buffer to a attrib location\r\n* @method bind\r\n* @param {number} location the location of the shader  (from shader.attributes[ name ])\r\n*/\r\nGL.Buffer.prototype.bind = function( location, gl )\r\n{\r\n\tgl = gl || this.gl;\r\n\r\n\tgl.bindBuffer( gl.ARRAY_BUFFER, this.buffer );\r\n\tgl.enableVertexAttribArray( location );\r\n\tgl.vertexAttribPointer( location, this.spacing, this.buffer.gl_type, false, 0, 0);\r\n}\r\n\r\n/**\r\n* unbinds the buffer from an attrib location\r\n* @method unbind\r\n* @param {number} location the location of the shader\r\n*/\r\nGL.Buffer.prototype.unbind = function( location, gl )\r\n{\r\n\tgl = gl || this.gl;\r\n\tgl.disableVertexAttribArray( location );\r\n}\r\n\r\n/**\r\n* Applies an action to every vertex in this buffer\r\n* @method forEach\r\n* @param {function} callback to be called for every vertex (or whatever is contained in the buffer)\r\n*/\r\nGL.Buffer.prototype.forEach = function(callback)\r\n{\r\n\tvar d = this.data;\r\n\tfor (var i = 0, s = this.spacing, l = d.length; i < l; i += s)\r\n\t{\r\n\t\tcallback(d.subarray(i,i+s),i);\r\n\t}\r\n\treturn this; //to concatenate\r\n}\r\n\r\n/**\r\n* Applies a mat4 transform to every triplets in the buffer (assuming they are points)\r\n* No upload is performed (to ensure efficiency in case there are several operations performed)\r\n* @method applyTransform\r\n* @param {mat4} mat\r\n*/\r\nGL.Buffer.prototype.applyTransform = function(mat)\r\n{\r\n\tvar d = this.data;\r\n\tfor (var i = 0, s = this.spacing, l = d.length; i < l; i += s)\r\n\t{\r\n\t\tvar v = d.subarray(i,i+s);\r\n\t\tvec3.transformMat4(v,v,mat);\r\n\t}\r\n\treturn this; //to concatenate\r\n}\r\n\r\n/**\r\n* Uploads the buffer data (stored in this.data) to the GPU\r\n* @method upload\r\n* @param {number} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW \r\n*/\r\nGL.Buffer.prototype.upload = function( stream_type ) { //default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW )\r\n\tvar spacing = this.spacing || 3; //default spacing\t\r\n\tvar gl = this.gl;\r\n\tif(!gl)\r\n\t\treturn;\r\n\r\n\tif(!this.data)\r\n\t\tthrow(\"No data supplied\");\r\n\r\n\tvar data = this.data;\r\n\tif(!data.buffer)\r\n\t\tthrow(\"Buffers must be typed arrays\");\r\n\r\n\t//I store some stuff inside the WebGL buffer instance, it is supported\r\n\tthis.buffer = this.buffer || gl.createBuffer();\r\n\tif(!this.buffer)\r\n\t\treturn; //if the context is lost...\r\n\r\n\tthis.buffer.length = data.length;\r\n\tthis.buffer.spacing = spacing;\r\n\r\n\t//store the data format\r\n\tswitch( data.constructor )\r\n\t{\r\n\t\tcase Int8Array: this.buffer.gl_type = gl.BYTE; break;\r\n\t\tcase Uint8ClampedArray: \r\n\t\tcase Uint8Array: this.buffer.gl_type = gl.UNSIGNED_BYTE; break;\r\n\t\tcase Int16Array: this.buffer.gl_type = gl.SHORT; break;\r\n\t\tcase Uint16Array: this.buffer.gl_type = gl.UNSIGNED_SHORT; break;\r\n\t\tcase Int32Array: this.buffer.gl_type = gl.INT; break;\r\n\t\tcase Uint32Array: this.buffer.gl_type = gl.UNSIGNED_INT; break;\r\n\t\tcase Float32Array: this.buffer.gl_type = gl.FLOAT; break;\r\n\t\tdefault: throw(\"unsupported buffer type\");\r\n\t}\r\n\r\n\tif(this.target == gl.ARRAY_BUFFER && ( this.buffer.gl_type == gl.INT || this.buffer.gl_type == gl.UNSIGNED_INT ))\r\n\t{\r\n\t\tconsole.warn(\"WebGL does not support UINT32 or INT32 as vertex buffer types, converting to FLOAT\");\r\n\t\tthis.buffer.gl_type = gl.FLOAT;\r\n\t\tdata = new Float32Array(data);\r\n\t}\r\n\r\n\tgl.bindBuffer(this.target, this.buffer);\r\n\tgl.bufferData(this.target, data , stream_type || this.stream_type || gl.STATIC_DRAW);\r\n};\r\n//legacy\r\nGL.Buffer.prototype.compile = GL.Buffer.prototype.upload;\r\n\r\n\r\n/**\r\n* Assign data to buffer and uploads it (it allows range)\r\n* @method setData\r\n* @param {ArrayBufferView} data in Float32Array format usually\r\n* @param {number} offset offset in bytes\r\n*/\r\nGL.Buffer.prototype.setData = function( data, offset )\r\n{\r\n\tif(!data.buffer)\r\n\t\tthrow(\"Data must be typed array\");\r\n\toffset = offset || 0;\r\n\r\n\tif(!this.data)\r\n\t{\r\n\t\tthis.data = data;\r\n\t\tthis.upload();\r\n\t\treturn;\r\n\t}\r\n\telse if( this.data.length < data.length )\r\n\t\tthrow(\"buffer is not big enough, you cannot set data to a smaller buffer\");\r\n\r\n\tif(this.data != data)\r\n\t{\r\n\t\tif(this.data.length == data.length)\r\n\t\t{\r\n\t\t\tthis.data.set( data );\r\n\t\t\tthis.upload();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t//upload just part of it\r\n\t\tvar new_data_view = new Uint8Array( data.buffer, data.buffer.byteOffset, data.buffer.byteLength );\r\n\t\tvar data_view = new Uint8Array( this.data.buffer );\r\n\t\tdata_view.set( new_data_view, offset );\r\n\t\tthis.uploadRange( offset, new_data_view.length );\r\n\t}\r\n\r\n};\r\n\r\n\r\n/**\r\n* Uploads part of the buffer data (stored in this.data) to the GPU\r\n* @method uploadRange\r\n* @param {number} start offset in bytes\r\n* @param {number} size sizes in bytes\r\n*/\r\nGL.Buffer.prototype.uploadRange = function(start, size)\r\n{\r\n\tif(!this.data)\r\n\t\tthrow(\"No data stored in this buffer\");\r\n\r\n\tvar data = this.data;\r\n\tif(!data.buffer)\r\n\t\tthrow(\"Buffers must be typed arrays\");\r\n\r\n\t//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)\r\n\tvar view = new Uint8Array( this.data.buffer, start, size );\r\n\r\n\tvar gl = this.gl;\r\n\tgl.bindBuffer(this.target, this.buffer);\r\n\tgl.bufferSubData(this.target, start, view );\r\n};\r\n\r\n/**\r\n* Clones one buffer (it allows to share the same data between both buffers)\r\n* @method clone\r\n* @param {boolean} share if you want that both buffers share the same data (default false)\r\n* return {GL.Buffer} buffer cloned\r\n*/\r\nGL.Buffer.prototype.clone = function(share)\r\n{\r\n\tvar buffer = new GL.Buffer();\r\n\tif(share)\r\n\t{\r\n\t\tfor(var i in this)\r\n\t\t\tbuffer[i] = this[i];\r\n\t}\r\n\telse\r\n\t{\r\n\t\tif(this.target)\r\n\t\t\tbuffer.target = this.target;\r\n\t\tif(this.gl)\r\n\t\t\tbuffer.gl = this.gl;\r\n\t\tif(this.spacing)\r\n\t\t\tbuffer.spacing = this.spacing;\r\n\t\tif(this.data) //clone data\r\n\t\t{\r\n\t\t\tbuffer.data = new global[ this.data.constructor ]( this.data );\r\n\t\t\tbuffer.upload();\r\n\t\t}\r\n\t}\r\n\treturn buffer;\r\n}\r\n\r\n\r\nGL.Buffer.prototype.toJSON = function()\r\n{\r\n\tif(!this.data)\r\n\t{\r\n\t\tconsole.error(\"cannot serialize a mesh without data\");\r\n\t\treturn null;\r\n\t}\r\n\r\n\treturn {\r\n\t\tdata_type: getClassName(this.data),\r\n\t\tdata: this.data.toJSON(),\r\n\t\ttarget: this.target,\r\n\t\tattribute: this.attribute,\r\n\t\tspacing: this.spacing\r\n\t};\r\n}\r\n\r\nGL.Buffer.prototype.fromJSON = function(o)\r\n{\r\n\tvar data_type = global[ o.data_type ] || Float32Array;\r\n\tthis.data = new data_type( o.data ); //cloned\r\n\tthis.target = o.target;\r\n\tthis.spacing = o.spacing || 3;\r\n\tthis.attribute = o.attribute;\r\n\tthis.upload( GL.STATIC_DRAW );\r\n}\r\n\r\n/**\r\n* Deletes the content from the GPU and destroys the handler\r\n* @method delete\r\n*/\r\nGL.Buffer.prototype.delete = function()\r\n{\r\n\tvar gl = this.gl;\r\n\tgl.deleteBuffer( this.buffer );\r\n\tthis.buffer = null;\r\n}\r\n\r\n/**\r\n* Base class for meshes, it wraps several buffers and some global info like the bounding box\r\n* @class Mesh\r\n* @param {Object} vertexBuffers object with all the vertex streams\r\n* @param {Object} indexBuffers object with all the indices streams\r\n* @param {Object} options\r\n* @param {WebGLContext} gl [Optional] gl context where to create the mesh\r\n* @constructor\r\n*/\r\nglobal.Mesh = GL.Mesh = function Mesh( vertexbuffers, indexbuffers, options, gl )\r\n{\r\n\tif(GL.debug)\r\n\t\tconsole.log(\"GL.Mesh created\");\r\n\r\n\tif( gl !== null )\r\n\t{\r\n\t\tgl = gl || global.gl;\r\n\t\tthis.gl = gl;\r\n\t}\r\n\r\n\t//used to avoid problems with resources moving between different webgl context\r\n\tthis._context_id = gl.context_id; \r\n\r\n\tthis.vertexBuffers = {};\r\n\tthis.indexBuffers = {};\r\n\r\n\t//here you can store extra info, like groups, which is an array of { name, start, length, material }\r\n\tthis.info = {\r\n\t\tgroups: []\r\n\t}; \r\n\tthis._bounding = BBox.create(); //here you can store a AABB in BBox format\r\n\r\n\tif(vertexbuffers || indexbuffers)\r\n\t\tthis.addBuffers( vertexbuffers, indexbuffers, options ? options.stream_type : null );\r\n\r\n\tif(options)\r\n\t\tfor(var i in options)\r\n\t\t\tthis[i] = options[i];\r\n};\r\n\r\nMesh.common_buffers = {\r\n\t\"vertices\": { spacing:3, attribute: \"a_vertex\"},\r\n\t\"vertices2D\": { spacing:2, attribute: \"a_vertex2D\"},\r\n\t\"normals\": { spacing:3, attribute: \"a_normal\"},\r\n\t\"coords\": { spacing:2, attribute: \"a_coord\"},\r\n\t\"coords1\": { spacing:2, attribute: \"a_coord1\"},\r\n\t\"coords2\": { spacing:2, attribute: \"a_coord2\"},\r\n\t\"colors\": { spacing:4, attribute: \"a_color\"}, \r\n\t\"tangents\": { spacing:3, attribute: \"a_tangent\"},\r\n\t\"bone_indices\": { spacing:4, attribute: \"a_bone_indices\", type: Uint8Array },\r\n\t\"weights\": { spacing:4, attribute: \"a_weights\"},\r\n\t\"extra\": { spacing:1, attribute: \"a_extra\"},\r\n\t\"extra2\": { spacing:2, attribute: \"a_extra2\"},\r\n\t\"extra3\": { spacing:3, attribute: \"a_extra3\"},\r\n\t\"extra4\": { spacing:4, attribute: \"a_extra4\"}\r\n};\r\n\r\nMesh.default_datatype = Float32Array;\r\n\r\nObject.defineProperty( Mesh.prototype, \"bounding\", {\r\n\tset: function(v)\r\n\t{\r\n\t\tif(!v)\r\n\t\t\treturn;\r\n\t\tif(v.length < 13)\r\n\t\t\tthrow(\"Bounding must use the BBox bounding format of 13 floats: center, halfsize, min, max, radius\");\r\n\t\tthis._bounding.set(v);\r\n\t},\r\n\tget: function()\r\n\t{\r\n\t\treturn this._bounding;\r\n\t}\r\n});\r\n\r\n/**\r\n* Adds buffer to mesh\r\n* @method addBuffer\r\n* @param {string} name\r\n* @param {Buffer} buffer \r\n*/\r\n\r\nMesh.prototype.addBuffer = function(name, buffer)\r\n{\r\n\tif(buffer.target == gl.ARRAY_BUFFER)\r\n\t\tthis.vertexBuffers[name] = buffer;\r\n\telse\r\n\t\tthis.indexBuffers[name] = buffer;\r\n\r\n\tif(!buffer.attribute)\r\n\t{\r\n\t\tvar info = GL.Mesh.common_buffers[name];\r\n\t\tif(info)\r\n\t\t\tbuffer.attribute = info.attribute;\r\n\t}\r\n}\r\n\r\n\r\n/**\r\n* Adds vertex and indices buffers to a mesh\r\n* @method addBuffers\r\n* @param {Object} vertexBuffers object with all the vertex streams\r\n* @param {Object} indexBuffers object with all the indices streams\r\n* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW )\r\n*/\r\nMesh.prototype.addBuffers = function( vertexbuffers, indexbuffers, stream_type )\r\n{\r\n\tvar num_vertices = 0;\r\n\r\n\tif(this.vertexBuffers[\"vertices\"])\r\n\t\tnum_vertices = this.vertexBuffers[\"vertices\"].data.length / 3;\r\n\r\n\tfor(var i in vertexbuffers)\r\n\t{\r\n\t\tvar data = vertexbuffers[i];\r\n\t\tif(!data) \r\n\t\t\tcontinue;\r\n\t\t\r\n\t\tif( data.constructor == GL.Buffer )\r\n\t\t{\r\n\t\t\tdata = data.data;\r\n\t\t}\r\n\t\telse if( typeof(data[0]) != \"number\") //linearize: (transform Arrays in typed arrays)\r\n\t\t{\r\n\t\t\tvar newdata = [];\r\n\t\t\tfor (var j = 0, chunk = 10000; j < data.length; j += chunk) {\r\n\t\t\t  newdata = Array.prototype.concat.apply(newdata, data.slice(j, j + chunk));\r\n\t\t\t}\r\n\t\t\tdata = newdata;\r\n\t\t}\r\n\r\n\t\tvar stream_info = GL.Mesh.common_buffers[i];\r\n\r\n\t\t//cast to typed float32 if no type is specified\r\n\t\tif(data.constructor === Array)\r\n\t\t{\r\n\t\t\tvar datatype = GL.Mesh.default_datatype;\r\n\t\t\tif(stream_info && stream_info.type)\r\n\t\t\t\tdatatype = stream_info.type;\r\n\t\t\tdata = new datatype( data );\r\n\t\t}\r\n\r\n\t\t//compute spacing\r\n\t\tif(i == \"vertices\")\r\n\t\t\tnum_vertices = data.length / 3;\r\n\t\tvar spacing = data.length / num_vertices;\r\n\t\tif(stream_info && stream_info.spacing)\r\n\t\t\tspacing = stream_info.spacing;\r\n\r\n\t\t//add and upload\r\n\t\tvar attribute = \"a_\" + i;\r\n\t\tif(stream_info && stream_info.attribute)\r\n\t\t\tattribute = stream_info.attribute;\r\n\t\r\n\t\tif( this.vertexBuffers[i] )\r\n\t\t\tthis.updateVertexBuffer( i, attribute, spacing, data, stream_type );\r\n\t\telse\r\n\t\t\tthis.createVertexBuffer( i, attribute, spacing, data, stream_type );\r\n\t}\r\n\r\n\tif(indexbuffers)\r\n\t\tfor(var i in indexbuffers)\r\n\t\t{\r\n\t\t\tvar data = indexbuffers[i];\r\n\t\t\tif(!data) continue;\r\n\r\n\t\t\tif( data.constructor == GL.Buffer )\r\n\t\t\t{\r\n\t\t\t\tdata = data.data;\r\n\t\t\t}\r\n\t\t\tif( typeof(data[0]) != \"number\") //linearize\r\n\t\t\t{\r\n\t\t\t\tnewdata = [];\r\n\t\t\t\tfor (var i = 0, chunk = 10000; i < data.length; i += chunk) {\r\n\t\t\t\t  newdata = Array.prototype.concat.apply(newdata, data.slice(i, i + chunk));\r\n\t\t\t\t}\r\n\t\t\t\tdata = newdata;\r\n\t\t\t}\r\n\r\n\t\t\t//cast to typed\r\n\t\t\tif(data.constructor === Array)\r\n\t\t\t{\r\n\t\t\t\tvar datatype = Uint16Array;\r\n\t\t\t\tif(num_vertices > 256*256)\r\n\t\t\t\t\tdatatype = Uint32Array;\r\n\t\t\t\tdata = new datatype( data );\r\n\t\t\t}\r\n\r\n\t\t\tthis.createIndexBuffer( i, data );\r\n\t\t}\r\n}\r\n\r\n/**\r\n* Creates a new empty buffer and attachs it to this mesh\r\n* @method createVertexBuffer\r\n* @param {String} name \"vertices\",\"normals\"...\r\n* @param {String} attribute name of the stream in the shader \"a_vertex\",\"a_normal\",... [optional, if omitted is used the common_buffers]\r\n* @param {number} spacing components per vertex [optional, if ommited is used the common_buffers, if not found then uses 3 ]\r\n* @param {ArrayBufferView} buffer_data the data in typed array format [optional, if ommited it created an empty array of getNumVertices() * spacing]\r\n* @param {enum} stream_type [optional, default = gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW ) ]\r\n*/\r\n\r\nMesh.prototype.createVertexBuffer = function( name, attribute, buffer_spacing, buffer_data, stream_type ) {\r\n\r\n\tvar common = GL.Mesh.common_buffers[name]; //generic info about a buffer with the same name\r\n\r\n\tif (!attribute && common)\r\n\t\tattribute = common.attribute;\r\n\r\n\tif (!attribute)\r\n\t\tthrow(\"Buffer added to mesh without attribute name\");\r\n\r\n\tif (!buffer_spacing && common)\r\n\t{\r\n\t\tif(common && common.spacing)\r\n\t\t\tbuffer_spacing = common.spacing;\r\n\t\telse\r\n\t\t\tbuffer_spacing = 3;\r\n\t}\r\n\r\n\tif(!buffer_data)\r\n\t{\r\n\t\tvar num = this.getNumVertices();\r\n\t\tif(!num)\r\n\t\t\tthrow(\"Cannot create an empty buffer in a mesh without vertices (vertices are needed to know the size)\");\r\n\t\tbuffer_data = new (GL.Mesh.default_datatype)(num * buffer_spacing);\r\n\t}\r\n\r\n\tif(!buffer_data.buffer)\r\n\t\tthrow(\"Buffer data MUST be typed array\");\r\n\r\n\t//used to ensure the buffers are held in the same gl context as the mesh\r\n\tvar buffer = this.vertexBuffers[name] = new GL.Buffer( gl.ARRAY_BUFFER, buffer_data, buffer_spacing, stream_type, this.gl );\r\n\tbuffer.name = name;\r\n\tbuffer.attribute = attribute;\r\n\r\n\treturn buffer;\r\n}\r\n\r\n/**\r\n* Updates a vertex buffer \r\n* @method updateVertexBuffer\r\n* @param {String} name the name of the buffer\r\n* @param {String} attribute the name of the attribute in the shader\r\n* @param {number} spacing number of numbers per component (3 per vertex, 2 per uvs...), default 3\r\n* @param {*} data the array with all the data\r\n* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW \r\n*/\r\nMesh.prototype.updateVertexBuffer = function( name, attribute, buffer_spacing, buffer_data, stream_type ) {\r\n\tvar buffer = this.vertexBuffers[name];\r\n\tif(!buffer)\r\n\t{\r\n\t\tconsole.log(\"buffer not found: \",name);\r\n\t\treturn;\r\n\t}\r\n\r\n\tif(!buffer_data.length)\r\n\t\treturn;\r\n\r\n\tbuffer.attribute = attribute;\r\n\tbuffer.spacing = buffer_spacing;\r\n\tbuffer.data = buffer_data;\r\n\tbuffer.upload( stream_type );\r\n}\r\n\r\n\r\n/**\r\n* Removes a vertex buffer from the mesh\r\n* @method removeVertexBuffer\r\n* @param {String} name \"vertices\",\"normals\"...\r\n* @param {Boolean} free if you want to remove the data from the GPU\r\n*/\r\nMesh.prototype.removeVertexBuffer = function(name, free) {\r\n\tvar buffer = this.vertexBuffers[name];\r\n\tif(!buffer)\r\n\t\treturn;\r\n\tif(free)\r\n\t\tbuffer.delete();\r\n\tdelete this.vertexBuffers[name];\r\n}\r\n\r\n/**\r\n* Returns a vertex buffer\r\n* @method getVertexBuffer\r\n* @param {String} name of vertex buffer\r\n* @return {Buffer} the buffer\r\n*/\r\nMesh.prototype.getVertexBuffer = function(name)\r\n{\r\n\treturn this.vertexBuffers[name];\r\n}\r\n\r\n\r\n/**\r\n* Creates a new empty index buffer and attachs it to this mesh\r\n* @method createIndexBuffer\r\n* @param {String} name \r\n* @param {Typed array} data \r\n* @param {enum} stream_type gl.STATIC_DRAW, gl.DYNAMIC_DRAW, gl.STREAM_DRAW\r\n*/\r\nMesh.prototype.createIndexBuffer = function(name, buffer_data, stream_type) {\r\n\t//(target, data, spacing, stream_type, gl)\r\n\r\n\t//cast to typed\r\n\tif(buffer_data.constructor === Array)\r\n\t{\r\n\t\tvar datatype = Uint16Array;\r\n\t\tvar vertices = this.vertexBuffers[\"vertices\"];\r\n\t\tif(vertices)\r\n\t\t{\r\n\t\t\tvar num_vertices = vertices.data.length / 3;\r\n\t\t\tif(num_vertices > 256*256)\r\n\t\t\t\tdatatype = Uint32Array;\r\n\t\t\tbuffer_data = new datatype( buffer_data );\r\n\t\t}\r\n\t}\r\n\r\n\tvar buffer = this.indexBuffers[name] = new GL.Buffer(gl.ELEMENT_ARRAY_BUFFER, buffer_data, 0, stream_type, this.gl );\r\n\treturn buffer;\r\n}\r\n\r\n/**\r\n* Returns a vertex buffer\r\n* @method getBuffer\r\n* @param {String} name of vertex buffer\r\n* @return {Buffer} the buffer\r\n*/\r\nMesh.prototype.getBuffer = function(name)\r\n{\r\n\treturn this.vertexBuffers[name];\r\n}\r\n\r\n/**\r\n* Returns a index buffer\r\n* @method getIndexBuffer\r\n* @param {String} name of index buffer\r\n* @return {Buffer} the buffer\r\n*/\r\nMesh.prototype.getIndexBuffer = function(name)\r\n{\r\n\treturn this.indexBuffers[name];\r\n}\r\n\r\n/**\r\n* Removes an index buffer from the mesh\r\n* @method removeIndexBuffer\r\n* @param {String} name \"vertices\",\"normals\"...\r\n* @param {Boolean} free if you want to remove the data from the GPU\r\n*/\r\nMesh.prototype.removeIndexBuffer = function(name, free) {\r\n\tvar buffer = this.indexBuffers[name];\r\n\tif(!buffer)\r\n\t\treturn;\r\n\tif(free)\r\n\t\tbuffer.delete();\r\n\tdelete this.indexBuffers[name];\r\n}\r\n\r\n\r\n/**\r\n* Uploads data inside buffers to VRAM.\r\n* @method upload\r\n* @param {number} buffer_type gl.STATIC_DRAW, gl.DYNAMIC_DRAW, gl.STREAM_DRAW\r\n*/\r\nMesh.prototype.upload = function(buffer_type) {\r\n\tfor (var attribute in this.vertexBuffers) {\r\n\t\tvar buffer = this.vertexBuffers[attribute];\r\n\t\t//buffer.data = this[buffer.name];\r\n\t\tbuffer.upload(buffer_type);\r\n\t}\r\n\r\n\tfor (var name in this.indexBuffers) {\r\n\t\tvar buffer = this.indexBuffers[name];\r\n\t\t//buffer.data = this[name];\r\n\t\tbuffer.upload();\r\n\t}\r\n}\r\n\r\n//LEGACY, plz remove\r\nMesh.prototype.compile = Mesh.prototype.upload;\r\n\r\n\r\nMesh.prototype.deleteBuffers = function()\r\n{\r\n\tfor(var i in this.vertexBuffers)\r\n\t{\r\n\t\tvar buffer = this.vertexBuffers[i];\r\n\t\tbuffer.delete();\r\n\t}\r\n\tthis.vertexBuffers = {};\r\n\r\n\tfor(var i in this.indexBuffers)\r\n\t{\r\n\t\tvar buffer = this.indexBuffers[i];\r\n\t\tbuffer.delete();\r\n\t}\r\n\tthis.indexBuffers = {};\r\n}\r\n\r\nMesh.prototype.delete = Mesh.prototype.deleteBuffers;\r\n\r\nMesh.prototype.bindBuffers = function( shader )\r\n{\r\n\t// enable attributes as necessary.\r\n\tfor (var name in this.vertexBuffers)\r\n\t{\r\n\t\tvar buffer = this.vertexBuffers[ name ];\r\n\t\tvar attribute = buffer.attribute || name;\r\n\t\tvar location = shader.attributes[ attribute ];\r\n\t\tif (location == null || !buffer.buffer) \r\n\t\t\tcontinue; \r\n\t\tgl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);\r\n\t\tgl.enableVertexAttribArray(location);\r\n\t\tgl.vertexAttribPointer(location, buffer.buffer.spacing, buffer.buffer.gl_type, false, 0, 0);\r\n\t}\r\n}\r\n\r\nMesh.prototype.unbindBuffers = function( shader )\r\n{\r\n\t// disable attributes\r\n\tfor (var name in this.vertexBuffers)\r\n\t{\r\n\t\tvar buffer = this.vertexBuffers[ name ];\r\n\t\tvar attribute = buffer.attribute || name;\r\n\t\tvar location = shader.attributes[ attribute ];\r\n\t\tif (location == null || !buffer.buffer)\r\n\t\t\tcontinue; //ignore this buffer\r\n\t\tgl.disableVertexAttribArray( shader.attributes[attribute] );\r\n\t}\r\n}\r\n\r\n/**\r\n* Creates a clone of the mesh, the datarrays are cloned too\r\n* @method clone\r\n*/\r\nMesh.prototype.clone = function( gl )\r\n{\r\n\tvar gl = gl || global.gl;\r\n\tvar vbs = {};\r\n\tvar ibs = {};\r\n\r\n\tfor(var i in this.vertexBuffers)\r\n\t{\r\n\t\tvar b = this.vertexBuffers[i];\r\n\t\tvbs[i] = new b.data.constructor( b.data ); //clone\r\n\t}\r\n\tfor(var i in this.indexBuffers)\r\n\t{\r\n\t\tvar b = this.indexBuffers[i];\r\n\t\tibs[i] = new b.data.constructor( b.data ); //clone\r\n\t}\r\n\r\n\treturn new GL.Mesh( vbs, ibs, undefined, gl );\r\n}\r\n\r\n/**\r\n* Creates a clone of the mesh, but the data-arrays are shared between both meshes (useful for sharing a mesh between contexts)\r\n* @method clone\r\n*/\r\nMesh.prototype.cloneShared = function( gl )\r\n{\r\n\tvar gl = gl || global.gl;\r\n\treturn new GL.Mesh( this.vertexBuffers, this.indexBuffers, undefined, gl );\r\n}\r\n\r\n\r\n/**\r\n* Creates an object with the info of the mesh (useful to transfer to workers)\r\n* @method toObject\r\n*/\r\nMesh.prototype.toObject = function()\r\n{\r\n\tvar vbs = {};\r\n\tvar ibs = {};\r\n\r\n\tfor(var i in this.vertexBuffers)\r\n\t{\r\n\t\tvar b = this.vertexBuffers[i];\r\n\t\tvbs[i] = { \r\n\t\t\tspacing: b.spacing,\r\n\t\t\tdata: new b.data.constructor( b.data ) //clone\r\n\t\t}; \r\n\t}\r\n\tfor(var i in this.indexBuffers)\r\n\t{\r\n\t\tvar b = this.indexBuffers[i];\r\n\t\tibs[i] = { \r\n\t\t\tdata: new b.data.constructor( b.data ) //clone\r\n\t\t}\r\n\t}\r\n\r\n\treturn { \r\n\t\tvertexBuffers: vbs, \r\n\t\tindexBuffers: ibs,\r\n\t\tinfo: this.info ? cloneObject( this.info ) : null,\r\n\t\tbounding: this._bounding.toJSON()\r\n\t};\r\n}\r\n\r\n\r\nMesh.prototype.toJSON = function()\r\n{\r\n\tvar r = {\r\n\t\tvertexBuffers: {},\r\n\t\tindexBuffers: {},\r\n\t\tinfo: this.info ? cloneObject( this.info ) : null,\r\n\t\tbounding: this._bounding.toJSON() \r\n\t};\r\n\r\n\tfor(var i in this.vertexBuffers)\r\n\t\tr.vertexBuffers[i] = this.vertexBuffers[i].toJSON();\r\n\r\n\tfor(var i in this.indexBuffers)\r\n\t\tr.indexBuffers[i] = this.indexBuffers[i].toJSON();\r\n\r\n\treturn r;\r\n}\r\n\r\nMesh.prototype.fromJSON = function(o)\r\n{\r\n\tthis.vertexBuffers = {};\r\n\tthis.indexBuffers = {};\r\n\r\n\tfor(var i in o.vertexBuffers)\r\n\t{\r\n\t\tif(!o.vertexBuffers[i])\r\n\t\t\tcontinue;\r\n\t\tvar buffer = new GL.Buffer();\r\n\t\tbuffer.fromJSON( o.vertexBuffers[i] );\r\n\t\tif(!buffer.attribute && GL.Mesh.common_buffers[i])\r\n\t\t\tbuffer.attribute = GL.Mesh.common_buffers[i].attribute;\r\n\t\tthis.vertexBuffers[i] = buffer;\r\n\t}\r\n\r\n\tfor(var i in o.indexBuffers)\r\n\t{\r\n\t\tif(!o.indexBuffers[i])\r\n\t\t\tcontinue;\r\n\t\tvar buffer = new GL.Buffer();\r\n\t\tbuffer.fromJSON( o.indexBuffers[i] );\r\n\t\tthis.indexBuffers[i] = buffer;\r\n\t}\r\n\r\n\tif(o.info)\r\n\t\tthis.info = cloneObject( o.info );\r\n\tif(o.bounding)\r\n\t\tthis.bounding = o.bounding; //setter does the job\r\n}\r\n\r\n\r\n/**\r\n* Computes some data about the mesh\r\n* @method generateMetadata\r\n*/\r\nMesh.prototype.generateMetadata = function()\r\n{\r\n\tvar metadata = {};\r\n\r\n\tvar vertices = this.vertexBuffers[\"vertices\"].data;\r\n\tvar triangles = this.indexBuffers[\"triangles\"].data;\r\n\r\n\tmetadata.vertices = vertices.length / 3;\r\n\tif(triangles)\r\n\t\tmetadata.faces = triangles.length / 3;\r\n\telse\r\n\t\tmetadata.faces = vertices.length / 9;\r\n\r\n\tmetadata.indexed = !!this.metadata.faces;\r\n\tthis.metadata = metadata;\r\n}\r\n\r\n//never tested\r\n/*\r\nMesh.prototype.draw = function(shader, mode, range_start, range_length)\r\n{\r\n\tif(range_length == 0) return;\r\n\r\n\t// Create and enable attribute pointers as necessary.\r\n\tvar length = 0;\r\n\tfor (var attribute in this.vertexBuffers) {\r\n\t  var buffer = this.vertexBuffers[attribute];\r\n\t  var location = shader.attributes[attribute] ||\r\n\t\tgl.getAttribLocation(shader.program, attribute);\r\n\t  if (location == -1 || !buffer.buffer) continue;\r\n\t  shader.attributes[attribute] = location;\r\n\t  gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);\r\n\t  gl.enableVertexAttribArray(location);\r\n\t  gl.vertexAttribPointer(location, buffer.buffer.spacing, gl.FLOAT, false, 0, 0);\r\n\t  length = buffer.buffer.length / buffer.buffer.spacing;\r\n\t}\r\n\r\n\t//range rendering\r\n\tvar offset = 0;\r\n\tif(arguments.length > 3) //render a polygon range\r\n\t\toffset = range_start * (this.indexBuffer ? this.indexBuffer.constructor.BYTES_PER_ELEMENT : 1); //in bytes (Uint16 == 2 bytes)\r\n\r\n\tif(arguments.length > 4)\r\n\t\tlength = range_length;\r\n\telse if (this.indexBuffer)\r\n\t\tlength = this.indexBuffer.buffer.length - offset;\r\n\r\n\t// Disable unused attribute pointers.\r\n\tfor (var attribute in shader.attributes) {\r\n\t  if (!(attribute in this.vertexBuffers)) {\r\n\t\tgl.disableVertexAttribArray(shader.attributes[attribute]);\r\n\t  }\r\n\t}\r\n\r\n\t// Draw the geometry.\r\n\tif (length && (!this.indexBuffer || indexBuffer.buffer)) {\r\n\t  if (this.indexBuffer) {\r\n\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer.buffer);\r\n\t\tgl.drawElements(mode, length, gl.UNSIGNED_SHORT, offset);\r\n\t  } else {\r\n\t\tgl.drawArrays(mode, offset, length);\r\n\t  }\r\n\t}\r\n\r\n\treturn this;\r\n}\r\n*/\r\n\r\n/**\r\n* Creates a new index stream with wireframe \r\n* @method computeWireframe\r\n*/\r\nMesh.prototype.computeWireframe = function() {\r\n\tvar index_buffer = this.indexBuffers[\"triangles\"];\r\n\r\n\tvar vertices = this.vertexBuffers[\"vertices\"].data;\r\n\tvar num_vertices = (vertices.length/3);\r\n\r\n\tif(!index_buffer) //unindexed\r\n\t{\r\n\t\tvar num_triangles = num_vertices / 3;\r\n\t\tvar buffer = num_vertices > 256*256 ? new Uint32Array( num_triangles * 6 ) : new Uint16Array( num_triangles * 6 );\r\n\t\tfor(var i = 0; i < num_vertices; i += 3)\r\n\t\t{\r\n\t\t\tbuffer[i*2] = i;\r\n\t\t\tbuffer[i*2+1] = i+1;\r\n\t\t\tbuffer[i*2+2] = i+1;\r\n\t\t\tbuffer[i*2+3] = i+2;\r\n\t\t\tbuffer[i*2+4] = i+2;\r\n\t\t\tbuffer[i*2+5] = i;\r\n\t\t}\r\n\r\n\t}\r\n\telse //indexed\r\n\t{\r\n\t\tvar data = index_buffer.data;\r\n\r\n\t\tvar indexer = new GL.Indexer();\r\n\t\tfor (var i = 0; i < data.length; i+=3) {\r\n\t\t  var t = data.subarray(i,i+3);\r\n\t\t  for (var j = 0; j < t.length; j++) {\r\n\t\t\tvar a = t[j], b = t[(j + 1) % t.length];\r\n\t\t\tindexer.add([Math.min(a, b), Math.max(a, b)]);\r\n\t\t  }\r\n\t\t}\r\n\r\n\t\t//linearize\r\n\t\tvar unique = indexer.unique;\r\n\t\tvar buffer = num_vertices > 256*256 ? new Uint32Array( unique.length * 2 ) : new Uint16Array( unique.length * 2 );\r\n\t\tfor(var i = 0, l = unique.length; i < l; ++i)\r\n\t\t\tbuffer.set(unique[i],i*2);\r\n\t}\r\n\r\n\t//create stream\r\n\tthis.createIndexBuffer('wireframe', buffer);\r\n\treturn this;\r\n}\r\n\r\n\r\n/**\r\n* Multiplies every normal by -1 and uploads it\r\n* @method flipNormals\r\n* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW)\r\n*/\r\nMesh.prototype.flipNormals = function( stream_type  ) {\r\n\tvar normals_buffer = this.vertexBuffers[\"normals\"];\r\n\tif(!normals_buffer)\r\n\t\treturn;\r\n\tvar data = normals_buffer.data;\r\n\tvar l = data.length;\r\n\tfor(var i = 0; i < l; ++i)\r\n\t\tdata[i] *= -1;\r\n\tnormals_buffer.upload( stream_type );\r\n\r\n\t//reverse indices too\r\n\tif( !this.indexBuffers[\"triangles\"] )\r\n\t\tthis.computeIndices(); //create indices\r\n\r\n\tvar triangles_buffer = this.indexBuffers[\"triangles\"];\r\n\tvar data = triangles_buffer.data;\r\n\tvar l = data.length;\r\n\tfor(var i = 0; i < l; i += 3)\r\n\t{\r\n\t\tvar tmp = data[i];\r\n\t\tdata[i] = data[i+1];\r\n\t\tdata[i+1] = tmp;\r\n\t\t//the [i+2] stays the same\r\n\t}\r\n\ttriangles_buffer.upload( stream_type );\r\n}\r\n\r\n\r\n/**\r\n* Compute indices for a mesh where vertices are shared\r\n* @method computeIndices\r\n*/\r\nMesh.prototype.computeIndices = function() {\r\n\r\n\t//cluster by distance\r\n\tvar new_vertices = [];\r\n\tvar new_normals = [];\r\n\tvar new_coords = [];\r\n\r\n\tvar indices = [];\r\n\r\n\tvar old_vertices_buffer = this.vertexBuffers[\"vertices\"];\r\n\tvar old_normals_buffer = this.vertexBuffers[\"normals\"];\r\n\tvar old_coords_buffer = this.vertexBuffers[\"coords\"];\r\n\r\n\tvar old_vertices_data = old_vertices_buffer.data;\r\n\r\n\tvar old_normals_data = null;\r\n\tif( old_normals_buffer )\r\n\t\told_normals_data = old_normals_buffer.data;\r\n\r\n\tvar old_coords_data = null;\r\n\tif( old_coords_buffer )\r\n\t\told_coords_data = old_coords_buffer.data;\r\n\r\n\r\n\tvar indexer = {};\r\n\r\n\tvar l = old_vertices_data.length / 3;\r\n\tfor(var i = 0; i < l; ++i)\r\n\t{\r\n\t\tvar v = old_vertices_data.subarray( i*3,(i+1)*3 );\r\n\t\tvar key = (v[0] * 1000)|0;\r\n\r\n\t\t//search in new_vertices\r\n\t\tvar j = 0;\r\n\t\tvar candidates = indexer[key];\r\n\t\tif(candidates)\r\n\t\t{\r\n\t\t\tvar l2 = candidates.length;\r\n\t\t\tfor(; j < l2; j++)\r\n\t\t\t{\r\n\t\t\t\tvar v2 = new_vertices[ candidates[j] ];\r\n\t\t\t\t//same vertex\r\n\t\t\t\tif( vec3.sqrDist( v, v2 ) < 0.01 )\r\n\t\t\t\t{\r\n\t\t\t\t\tindices.push(j);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t/*\r\n\t\tvar l2 = new_vertices.length;\r\n\t\tfor(var j = 0; j < l2; j++)\r\n\t\t{\r\n\t\t\t//same vertex\r\n\t\t\tif( vec3.sqrDist( v, new_vertices[j] ) < 0.001 )\r\n\t\t\t{\r\n\t\t\t\tindices.push(j);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\t*/\r\n\r\n\t\tif(candidates && j != l2)\r\n\t\t\tcontinue;\r\n\r\n\t\tvar index = j;\r\n\t\tnew_vertices.push(v);\r\n\t\tif( indexer[ key ] )\r\n\t\t\tindexer[ key ].push( index );\r\n\t\telse\r\n\t\t\tindexer[ key ] = [ index ];\r\n\r\n\t\tif(old_normals_data)\r\n\t\t\tnew_normals.push( old_normals_data.subarray(i*3, (i+1)*3) );\r\n\t\tif(old_coords_data)\r\n\t\t\tnew_coords.push( old_coords_data.subarray(i*2, (i+1)*2) );\r\n\t\tindices.push(index);\r\n\t}\r\n\r\n\tthis.vertexBuffers = {}; //erase all\r\n\r\n\t//new buffers\r\n\tthis.createVertexBuffer( 'vertices', GL.Mesh.common_buffers[\"vertices\"].attribute, 3, linearizeArray( new_vertices ) );\t\r\n\tif(old_normals_data)\r\n\t\tthis.createVertexBuffer( 'normals', GL.Mesh.common_buffers[\"normals\"].attribute, 3, linearizeArray( new_normals ) );\t\r\n\tif(old_coords_data)\r\n\t\tthis.createVertexBuffer( 'coords', GL.Mesh.common_buffers[\"coords\"].attribute, 2, linearizeArray( new_coords ) );\t\r\n\r\n\tthis.createIndexBuffer( \"triangles\", indices );\r\n}\r\n\r\n/**\r\n* Breaks the indices\r\n* @method explodeIndices\r\n*/\r\nMesh.prototype.explodeIndices = function( buffer_name ) {\r\n\r\n\tbuffer_name = buffer_name || \"triangles\";\r\n\r\n\tvar indices_buffer = this.getIndexBuffer( buffer_name );\r\n\tif(!indices_buffer)\r\n\t\treturn;\r\n\r\n\tvar indices = indices_buffer.data;\r\n\r\n\tvar new_buffers = {};\r\n\tfor(var i in this.vertexBuffers)\r\n\t{\r\n\t\tvar info = GL.Mesh.common_buffers[i];\r\n\t\tnew_buffers[i] = new (info.type || Float32Array)( info.spacing * indices.length );\r\n\t}\r\n\r\n\tfor(var i = 0, l = indices.length; i < l; ++i)\r\n\t{\r\n\t\tvar index = indices[i];\r\n\t\tfor(var j in this.vertexBuffers)\r\n\t\t{\r\n\t\t\tvar buffer = this.vertexBuffers[j];\r\n\t\t\tvar info = GL.Mesh.common_buffers[j];\r\n\t\t\tvar spacing = buffer.spacing || info.spacing;\r\n\t\t\tvar new_buffer = new_buffers[j];\r\n\t\t\tnew_buffer.set( buffer.data.subarray( index*spacing, index*spacing + spacing ), i*spacing );\r\n\t\t}\r\n\t}\r\n\r\n\t/*\r\n\t//cluster by distance\r\n\tvar new_vertices = new Float32Array(indices.length * 3);\r\n\tvar new_normals = null;\r\n\tvar new_coords = null;\r\n\r\n\tvar old_vertices_buffer = this.vertexBuffers[\"vertices\"];\r\n\tvar old_vertices = old_vertices_buffer.data;\r\n\r\n\tvar old_normals_buffer = this.vertexBuffers[\"normals\"];\r\n\tvar old_normals = null;\r\n\tif(old_normals_buffer)\r\n\t{\r\n\t\told_normals = old_normals_buffer.data;\r\n\t\tnew_normals = new Float32Array(indices.length * 3);\r\n\t}\r\n\r\n\tvar old_coords_buffer = this.vertexBuffers[\"coords\"];\r\n\tvar old_coords = null;\r\n\tif( old_coords_buffer )\r\n\t{\r\n\t\told_coords = old_coords_buffer.data;\r\n\t\tnew_coords = new Float32Array(indices.length * 2);\r\n\t}\r\n\r\n\tfor(var i = 0, l = indices.length; i < l; ++i)\r\n\t{\r\n\t\tvar index = indices[i];\r\n\t\tnew_vertices.set( old_vertices.subarray( index*3, index*3 + 3 ), i*3 );\r\n\t\tif(old_normals)\r\n\t\t\tnew_normals.set( old_normals.subarray( index*3, index*3 + 3 ), i*3 );\r\n\t\tif(old_coords)\r\n\t\t\tnew_coords.set( old_coords.subarray( index*2, index*2 + 2 ), i*2 );\r\n\t}\r\n\r\n\t//erase all\r\n\tthis.vertexBuffers = {}; \r\n\r\n\t//new buffers\r\n\tthis.createVertexBuffer( 'vertices', GL.Mesh.common_buffers[\"vertices\"].attribute, 3, new_vertices );\t\r\n\tif(new_normals)\r\n\t\tthis.createVertexBuffer( 'normals', GL.Mesh.common_buffers[\"normals\"].attribute, 3, new_normals );\t\r\n\tif(new_coords)\r\n\t\tthis.createVertexBuffer( 'coords', GL.Mesh.common_buffers[\"coords\"].attribute, 2, new_coords );\t\r\n\t*/\r\n\r\n\tfor(var i in new_buffers)\r\n\t{\r\n\t\tvar old = this.vertexBuffers[i];\r\n\t\tthis.createVertexBuffer( i, old.attribute, old.spacing, new_buffers[i] );\t\r\n\t}\r\n\r\n\tdelete this.indexBuffers[ buffer_name ];\r\n}\r\n\r\n\r\n\r\n/**\r\n* Creates a stream with the normals\r\n* @method computeNormals\r\n* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW)\r\n*/\r\nMesh.prototype.computeNormals = function( stream_type  ) {\r\n\tvar vertices_buffer = this.vertexBuffers[\"vertices\"];\r\n\tif(!vertices_buffer)\r\n\t\treturn console.error(\"Cannot compute normals of a mesh without vertices\");\r\n\r\n\tvar vertices = this.vertexBuffers[\"vertices\"].data;\r\n\tvar num_vertices = vertices.length / 3;\r\n\r\n\t//create because it is faster than filling it with zeros\r\n\tvar normals = new Float32Array( vertices.length );\r\n\r\n\tvar triangles = null;\r\n\tif(this.indexBuffers[\"triangles\"])\r\n\t\ttriangles = this.indexBuffers[\"triangles\"].data;\r\n\r\n\tvar temp = GL.temp_vec3;\r\n\tvar temp2 = GL.temp2_vec3;\r\n\r\n\tvar i1,i2,i3,v1,v2,v3,n1,n2,n3;\r\n\r\n\t//compute the plane normal\r\n\tvar l = triangles ? triangles.length : vertices.length;\r\n\tfor (var a = 0; a < l; a+=3)\r\n\t{\r\n\t\tif(triangles)\r\n\t\t{\r\n\t\t\ti1 = triangles[a];\r\n\t\t\ti2 = triangles[a+1];\r\n\t\t\ti3 = triangles[a+2];\r\n\r\n\t\t\tv1 = vertices.subarray(i1*3,i1*3+3);\r\n\t\t\tv2 = vertices.subarray(i2*3,i2*3+3);\r\n\t\t\tv3 = vertices.subarray(i3*3,i3*3+3);\r\n\r\n\t\t\tn1 = normals.subarray(i1*3,i1*3+3);\r\n\t\t\tn2 = normals.subarray(i2*3,i2*3+3);\r\n\t\t\tn3 = normals.subarray(i3*3,i3*3+3);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tv1 = vertices.subarray(a*3,a*3+3);\r\n\t\t\tv2 = vertices.subarray(a*3+3,a*3+6);\r\n\t\t\tv3 = vertices.subarray(a*3+6,a*3+9);\r\n\r\n\t\t\tn1 = normals.subarray(a*3,a*3+3);\r\n\t\t\tn2 = normals.subarray(a*3+3,a*3+6);\r\n\t\t\tn3 = normals.subarray(a*3+6,a*3+9);\r\n\t\t}\r\n\r\n\t\tvec3.sub( temp, v2, v1 );\r\n\t\tvec3.sub( temp2, v3, v1 );\r\n\t\tvec3.cross( temp, temp, temp2 );\r\n\t\tvec3.normalize(temp,temp);\r\n\r\n\t\t//save\r\n\t\tvec3.add( n1, n1, temp );\r\n\t\tvec3.add( n2, n2, temp );\r\n\t\tvec3.add( n3, n3, temp );\r\n\t}\r\n\r\n\t//normalize if vertices are shared\r\n\tif(triangles)\r\n\tfor (var a = 0, l = normals.length; a < l; a+=3)\r\n\t{\r\n\t\tvar n = normals.subarray(a,a+3);\r\n\t\tvec3.normalize(n,n);\r\n\t}\r\n\r\n\tvar normals_buffer = this.vertexBuffers[\"normals\"];\r\n\r\n\tif(normals_buffer)\r\n\t{\r\n\t\tnormals_buffer.data = normals;\r\n\t\tnormals_buffer.upload( stream_type );\r\n\t}\r\n\telse\r\n\t\treturn this.createVertexBuffer('normals', GL.Mesh.common_buffers[\"normals\"].attribute, 3, normals );\r\n\treturn normals_buffer;\r\n}\r\n\r\n\r\n/**\r\n* Creates a new stream with the tangents\r\n* @method computeTangents\r\n*/\r\nMesh.prototype.computeTangents = function()\r\n{\r\n\tvar vertices_buffer = this.vertexBuffers[\"vertices\"];\r\n\tif(!vertices_buffer)\r\n\t\treturn console.error(\"Cannot compute tangents of a mesh without vertices\");\r\n\r\n\tvar normals_buffer = this.vertexBuffers[\"normals\"];\r\n\tif(!normals_buffer)\r\n\t\treturn console.error(\"Cannot compute tangents of a mesh without normals\");\r\n\r\n\tvar uvs_buffer = this.vertexBuffers[\"coords\"];\r\n\tif(!uvs_buffer)\r\n\t\treturn console.error(\"Cannot compute tangents of a mesh without uvs\");\r\n\r\n\tvar triangles_buffer = this.indexBuffers[\"triangles\"];\r\n\tif(!triangles_buffer)\r\n\t\treturn console.error(\"Cannot compute tangents of a mesh without indices\");\r\n\r\n\tvar vertices = vertices_buffer.data;\r\n\tvar normals = normals_buffer.data;\r\n\tvar uvs = uvs_buffer.data;\r\n\tvar triangles = triangles_buffer.data;\r\n\r\n\tif(!vertices || !normals || !uvs) return;\r\n\r\n\tvar num_vertices = vertices.length / 3;\r\n\r\n\tvar tangents = new Float32Array(num_vertices * 4);\r\n\t\r\n\t//temporary (shared)\r\n\tvar tan1 = new Float32Array(num_vertices*3*2);\r\n\tvar tan2 = tan1.subarray(num_vertices*3);\r\n\r\n\tvar a,l;\r\n\tvar sdir = vec3.create();\r\n\tvar tdir = vec3.create();\r\n\tvar temp = vec3.create();\r\n\tvar temp2 = vec3.create();\r\n\r\n\tfor (a = 0, l = triangles.length; a < l; a+=3)\r\n\t{\r\n\t\tvar i1 = triangles[a];\r\n\t\tvar i2 = triangles[a+1];\r\n\t\tvar i3 = triangles[a+2];\r\n\r\n\t\tvar v1 = vertices.subarray(i1*3,i1*3+3);\r\n\t\tvar v2 = vertices.subarray(i2*3,i2*3+3);\r\n\t\tvar v3 = vertices.subarray(i3*3,i3*3+3);\r\n\r\n\t\tvar w1 = uvs.subarray(i1*2,i1*2+2);\r\n\t\tvar w2 = uvs.subarray(i2*2,i2*2+2);\r\n\t\tvar w3 = uvs.subarray(i3*2,i3*2+2);\r\n\r\n\t\tvar x1 = v2[0] - v1[0];\r\n\t\tvar x2 = v3[0] - v1[0];\r\n\t\tvar y1 = v2[1] - v1[1];\r\n\t\tvar y2 = v3[1] - v1[1];\r\n\t\tvar z1 = v2[2] - v1[2];\r\n\t\tvar z2 = v3[2] - v1[2];\r\n\r\n\t\tvar s1 = w2[0] - w1[0];\r\n\t\tvar s2 = w3[0] - w1[0];\r\n\t\tvar t1 = w2[1] - w1[1];\r\n\t\tvar t2 = w3[1] - w1[1];\r\n\r\n\t\tvar r;\r\n\t\tvar den = (s1 * t2 - s2 * t1);\r\n\t\tif ( Math.abs(den) < 0.000000001 )\r\n\t\t  r = 0.0;\r\n\t\telse\r\n\t\t  r = 1.0 / den;\r\n\r\n\t\tvec3.copy(sdir, [(t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r] );\r\n\t\tvec3.copy(tdir, [(s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r] );\r\n\r\n\t\tvec3.add( tan1.subarray( i1*3, i1*3+3), tan1.subarray( i1*3, i1*3+3), sdir);\r\n\t\tvec3.add( tan1.subarray( i2*3, i2*3+3), tan1.subarray( i2*3, i2*3+3), sdir);\r\n\t\tvec3.add( tan1.subarray( i3*3, i3*3+3), tan1.subarray( i3*3, i3*3+3), sdir);\r\n\r\n\t\tvec3.add( tan2.subarray( i1*3, i1*3+3), tan2.subarray( i1*3, i1*3+3), tdir);\r\n\t\tvec3.add( tan2.subarray( i2*3, i2*3+3), tan2.subarray( i2*3, i2*3+3), tdir);\r\n\t\tvec3.add( tan2.subarray( i3*3, i3*3+3), tan2.subarray( i3*3, i3*3+3), tdir);\r\n\t}\r\n\r\n\tfor (a = 0, l = vertices.length; a < l; a+=3)\r\n\t{\r\n\t\tvar n = normals.subarray(a,a+3);\r\n\t\tvar t = tan1.subarray(a,a+3);\r\n\r\n\t\t// Gram-Schmidt orthogonalize\r\n\t\tvec3.subtract(temp, t, vec3.scale(temp, n, vec3.dot(n, t) ) );\r\n\t\tvec3.normalize(temp,temp);\r\n\r\n\t\t// Calculate handedness\r\n\t\tvar w = ( vec3.dot( vec3.cross(temp2, n, t), tan2.subarray(a,a+3) ) < 0.0) ? -1.0 : 1.0;\r\n\t\ttangents.set([temp[0], temp[1], temp[2], w],(a/3)*4);\r\n\t}\r\n\r\n\tthis.createVertexBuffer('tangents', Mesh.common_buffers[\"tangents\"].attribute, 4, tangents );\r\n}\r\n\r\n/**\r\n* Creates texture coordinates using a triplanar aproximation\r\n* @method computeTextureCoordinates\r\n*/\r\nMesh.prototype.computeTextureCoordinates = function( stream_type )\r\n{\r\n\tvar vertices_buffer = this.vertexBuffers[\"vertices\"];\r\n\tif(!vertices_buffer)\r\n\t\treturn console.error(\"Cannot compute uvs of a mesh without vertices\");\r\n\r\n\tthis.explodeIndices( \"triangles\" );\r\n\r\n\tvertices_buffer = this.vertexBuffers[\"vertices\"];\r\n\tvar vertices = vertices_buffer.data;\r\n\tvar num_vertices = vertices.length / 3;\r\n\r\n\tvar uvs_buffer = this.vertexBuffers[\"coords\"];\r\n\tvar uvs = new Float32Array( num_vertices * 2 );\r\n\r\n\tvar triangles_buffer = this.indexBuffers[\"triangles\"];\r\n\tvar triangles = null;\r\n\tif( triangles_buffer )\r\n\t\ttriangles = triangles_buffer.data;\r\n\r\n\tvar plane_normal = vec3.create();\r\n\tvar side1 = vec3.create();\r\n\tvar side2 = vec3.create();\r\n\r\n\tvar bbox = this.getBoundingBox();\r\n\tvar bboxcenter = BBox.getCenter( bbox );\r\n\tvar bboxhs = vec3.create();\r\n\tbboxhs.set( BBox.getHalfsize( bbox ) ); //careful, this is a reference\r\n\tvec3.scale( bboxhs, bboxhs, 2 );\r\n\r\n\tvar num = triangles ? triangles.length : vertices.length/3;\r\n\r\n\tfor (var a = 0; a < num; a+=3)\r\n\t{\r\n\t\tif(triangles)\r\n\t\t{\r\n\t\t\tvar i1 = triangles[a];\r\n\t\t\tvar i2 = triangles[a+1];\r\n\t\t\tvar i3 = triangles[a+2];\r\n\r\n\t\t\tvar v1 = vertices.subarray(i1*3,i1*3+3);\r\n\t\t\tvar v2 = vertices.subarray(i2*3,i2*3+3);\r\n\t\t\tvar v3 = vertices.subarray(i3*3,i3*3+3);\r\n\r\n\t\t\tvar uv1 = uvs.subarray(i1*2,i1*2+2);\r\n\t\t\tvar uv2 = uvs.subarray(i2*2,i2*2+2);\r\n\t\t\tvar uv3 = uvs.subarray(i3*2,i3*2+2);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar v1 = vertices.subarray((a)*3,(a)*3+3);\r\n\t\t\tvar v2 = vertices.subarray((a+1)*3,(a+1)*3+3);\r\n\t\t\tvar v3 = vertices.subarray((a+2)*3,(a+2)*3+3);\r\n\r\n\t\t\tvar uv1 = uvs.subarray((a)*2,(a)*2+2);\r\n\t\t\tvar uv2 = uvs.subarray((a+1)*2,(a+1)*2+2);\r\n\t\t\tvar uv3 = uvs.subarray((a+2)*2,(a+2)*2+2);\r\n\t\t}\r\n\r\n\t\tvec3.sub(side1, v1, v2 );\r\n\t\tvec3.sub(side2, v1, v3 );\r\n\t\tvec3.cross( plane_normal, side1, side2 );\r\n\t\t//vec3.normalize( plane_normal, plane_normal ); //not necessary\r\n\r\n\t\tplane_normal[0] = Math.abs( plane_normal[0] );\r\n\t\tplane_normal[1] = Math.abs( plane_normal[1] );\r\n\t\tplane_normal[2] = Math.abs( plane_normal[2] );\r\n\r\n\t\tif( plane_normal[0] > plane_normal[1] && plane_normal[0] > plane_normal[2])\r\n\t\t{\r\n\t\t\t//X\r\n\t\t\tuv1[0] = (v1[2] - bboxcenter[2]) / bboxhs[2];\r\n\t\t\tuv1[1] = (v1[1] - bboxcenter[1]) / bboxhs[1];\r\n\t\t\tuv2[0] = (v2[2] - bboxcenter[2]) / bboxhs[2];\r\n\t\t\tuv2[1] = (v2[1] - bboxcenter[1]) / bboxhs[1];\r\n\t\t\tuv3[0] = (v3[2] - bboxcenter[2]) / bboxhs[2];\r\n\t\t\tuv3[1] = (v3[1] - bboxcenter[1]) / bboxhs[1];\r\n\t\t}\r\n\t\telse if ( plane_normal[1] > plane_normal[2])\r\n\t\t{\r\n\t\t\t//Y\r\n\t\t\tuv1[0] = (v1[0] - bboxcenter[0]) / bboxhs[0];\r\n\t\t\tuv1[1] = (v1[2] - bboxcenter[2]) / bboxhs[2];\r\n\t\t\tuv2[0] = (v2[0] - bboxcenter[0]) / bboxhs[0];\r\n\t\t\tuv2[1] = (v2[2] - bboxcenter[2]) / bboxhs[2];\r\n\t\t\tuv3[0] = (v3[0] - bboxcenter[0]) / bboxhs[0];\r\n\t\t\tuv3[1] = (v3[2] - bboxcenter[2]) / bboxhs[2];\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t//Z\r\n\t\t\tuv1[0] = (v1[0] - bboxcenter[0]) / bboxhs[0];\r\n\t\t\tuv1[1] = (v1[1] - bboxcenter[1]) / bboxhs[1];\r\n\t\t\tuv2[0] = (v2[0] - bboxcenter[0]) / bboxhs[0];\r\n\t\t\tuv2[1] = (v2[1] - bboxcenter[1]) / bboxhs[1];\r\n\t\t\tuv3[0] = (v3[0] - bboxcenter[0]) / bboxhs[0];\r\n\t\t\tuv3[1] = (v3[1] - bboxcenter[1]) / bboxhs[1];\r\n\t\t}\r\n\t}\r\n\r\n\tif(uvs_buffer)\r\n\t{\r\n\t\tuvs_buffer.data = uvs;\r\n\t\tuvs_buffer.upload( stream_type );\r\n\t}\r\n\telse\r\n\t\tthis.createVertexBuffer('coords', Mesh.common_buffers[\"coords\"].attribute, 2, uvs );\r\n}\r\n\r\n\r\n/**\r\n* Computes the number of vertices\r\n* @method getVertexNumber\r\n*/\r\nMesh.prototype.getNumVertices = function() {\r\n\tvar b = this.vertexBuffers[\"vertices\"];\r\n\tif(!b)\r\n\t\treturn 0;\r\n\treturn b.data.length / b.spacing;\r\n}\r\n\r\n/**\r\n* Computes the number of triangles (takes into account indices)\r\n* @method getNumTriangles\r\n*/\r\nMesh.prototype.getNumTriangles = function() {\r\n\tvar indices_buffer = this.getIndexBuffer(\"triangles\");\r\n\tif(!indices_buffer)\r\n\t\treturn this.getNumVertices() / 3;\r\n\treturn indices_buffer.data.length / 3;\r\n}\r\n\r\n\r\n/**\r\n* Computes bounding information\r\n* @method Mesh.computeBoundingBox\r\n* @param {typed Array} vertices array containing all the vertices\r\n* @param {BBox} bb where to store the bounding box\r\n* @param {Array} mask [optional] to specify which vertices must be considered when creating the bbox, used to create BBox of a submesh\r\n*/\r\nMesh.computeBoundingBox = function( vertices, bb, mask ) {\r\n\r\n\tif(!vertices)\r\n\t\treturn;\r\n\r\n\tvar start = 0;\r\n\r\n\tif(mask)\r\n\t{\r\n\t\tfor(var i = 0; i < mask.length; ++i)\r\n\t\t\tif( mask[i] )\r\n\t\t\t{\r\n\t\t\t\tstart = i;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\tif(start == mask.length)\r\n\t\t{\r\n\t\t\tconsole.warn(\"mask contains only zeros, no vertices marked\");\r\n\t\t\treturn;\r\n\t\t}\r\n\t}\r\n\r\n\tvar min = vec3.clone( vertices.subarray( start*3, start*3 + 3) );\r\n\tvar max = vec3.clone( vertices.subarray( start*3, start*3 + 3) );\r\n\tvar v;\r\n\r\n\tfor(var i = start*3; i < vertices.length; i+=3)\r\n\t{\r\n\t\tif( mask && !mask[i/3] )\r\n\t\t\tcontinue;\r\n\t\tv = vertices.subarray(i,i+3);\r\n\t\tvec3.min( min,v, min);\r\n\t\tvec3.max( max,v, max);\r\n\t}\r\n\r\n\tif( isNaN(min[0]) || isNaN(min[1]) || isNaN(min[2]) ||\r\n\t\tisNaN(max[0]) || isNaN(max[1]) || isNaN(max[2]) )\r\n\t{\r\n\t\tmin[0] = min[1] = min[2] = 0;\r\n\t\tmax[0] = max[1] = max[2] = 0;\r\n\t\tconsole.warn(\"Warning: GL.Mesh has NaN values in vertices\");\r\n\t}\r\n\r\n\tvar center = vec3.add( vec3.create(), min,max );\r\n\tvec3.scale( center, center, 0.5);\r\n\tvar half_size = vec3.subtract( vec3.create(), max, center );\r\n\r\n\treturn BBox.setCenterHalfsize( bb || BBox.create(), center, half_size );\r\n}\r\n\r\n/**\r\n* returns the bounding box, if it is not computed, then computes it\r\n* @method getBoundingBox\r\n* @return {BBox} bounding box\r\n*/\r\nMesh.prototype.getBoundingBox = function()\r\n{\r\n\tif(this._bounding)\r\n\t\treturn this._bounding;\r\n\r\n\tthis.updateBoundingBox();\r\n\treturn this._bounding;\r\n}\r\n\r\n/**\r\n* Update bounding information of this mesh\r\n* @method updateBoundingBox\r\n*/\r\nMesh.prototype.updateBoundingBox = function() {\r\n\tvar vertices = this.vertexBuffers[\"vertices\"];\r\n\tif(!vertices)\r\n\t\treturn;\r\n\tGL.Mesh.computeBoundingBox( vertices.data, this._bounding );\r\n\tif(this.info && this.info.groups && this.info.groups.length)\r\n\t\tthis.computeGroupsBoundingBoxes();\r\n}\r\n\r\n/**\r\n* Update bounding information for every group submesh\r\n* @method computeGroupsBoundingBoxes\r\n*/\r\nMesh.prototype.computeGroupsBoundingBoxes = function()\r\n{\r\n\tvar indices = null;\r\n\tvar indices_buffer = this.getIndexBuffer(\"triangles\");\r\n\tif( indices_buffer )\r\n\t\tindices = indices_buffer.data;\r\n\r\n\tvar vertices_buffer = this.getVertexBuffer(\"vertices\");\r\n\tif(!vertices_buffer)\r\n\t\treturn false;\r\n\tvar vertices = vertices_buffer.data;\r\n\tif(!vertices.length)\r\n\t\treturn false;\r\n\r\n\tvar groups = this.info.groups;\r\n\tfor(var i = 0; i < groups.length; ++i)\r\n\t{\r\n\t\tvar group = groups[i];\r\n\t\tgroup.bounding = group.bounding || BBox.create();\r\n\t\tvar submesh_vertices = null;\r\n\t\tif( indices )\r\n\t\t{\r\n\t\t\tvar mask = new Uint8Array( vertices.length / 3 );\r\n\t\t\tvar s = group.start;\r\n\t\t\tfor( var j = 0, l = group.length; j < l; j += 3 )\r\n\t\t\t{\r\n\t\t\t\tmask[ indices[s+j] ] = 1;\r\n\t\t\t\tmask[ indices[s+j+1] ] = 1;\r\n\t\t\t\tmask[ indices[s+j+2] ] = 1;\r\n\t\t\t}\r\n\t\t\tGL.Mesh.computeBoundingBox( vertices, group.bounding, mask );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tsubmesh_vertices = vertices.subarray( group.start * 3, ( group.start + group.length) * 3 );\r\n\t\t\tGL.Mesh.computeBoundingBox( submesh_vertices, group.bounding );\r\n\t\t}\r\n\t}\r\n\treturn true;\r\n}\r\n\r\n\r\n\r\n/**\r\n* forces a bounding box to be set\r\n* @method setBoundingBox\r\n* @param {vec3} center center of the bounding box\r\n* @param {vec3} half_size vector from the center to positive corner\r\n*/\r\nMesh.prototype.setBoundingBox = function( center, half_size ) {\r\n\tBBox.setCenterHalfsize( this._bounding, center, half_size );\t\r\n}\r\n\r\n\r\n/**\r\n* Remove all local memory from the streams (leaving it only in the VRAM) to save RAM\r\n* @method freeData\r\n*/\r\nMesh.prototype.freeData = function()\r\n{\r\n\tfor (var attribute in this.vertexBuffers)\r\n\t{\r\n\t\tthis.vertexBuffers[attribute].data = null;\r\n\t\tdelete this[ this.vertexBuffers[attribute].name ]; //delete from the mesh itself\r\n\t}\r\n\tfor (var name in this.indexBuffers)\r\n\t{\r\n\t\tthis.indexBuffers[name].data = null;\r\n\t\tdelete this[ this.indexBuffers[name].name ]; //delete from the mesh itself\r\n\t}\r\n}\r\n\r\nMesh.prototype.configure = function( o, options )\r\n{\r\n\tvar vertex_buffers = {};\r\n\tvar index_buffers = {};\r\n\toptions = options || {};\r\n\r\n\tfor(var j in o)\r\n\t{\r\n\t\tif(!o[j])\r\n\t\t\tcontinue;\r\n\r\n\t\tif(j == \"vertexBuffers\" || j == \"vertex_buffers\") //HACK: legacy code\r\n\t\t{\r\n\t\t\tfor(i in o[j])\r\n\t\t\t\tvertex_buffers[i] = o[j][i];\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\t\r\n\t\tif(j == \"indexBuffers\" || j == \"index_buffers\")\r\n\t\t{\r\n\t\t\tfor(i in o[j])\r\n\t\t\t\tindex_buffers[i] = o[j][i];\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\tif(j == \"indices\" || j == \"lines\" ||  j == \"wireframe\" || j == \"triangles\")\r\n\t\t\tindex_buffers[j] = o[j];\r\n\t\telse if( GL.Mesh.common_buffers[j])\r\n\t\t\tvertex_buffers[j] = o[j];\r\n\t\telse //global data like bounding, info of groups, etc\r\n\t\t{\r\n\t\t\toptions[j] = o[j];\r\n\t\t}\r\n\t}\r\n\r\n\tthis.addBuffers( vertex_buffers, index_buffers, options.stream_type );\r\n\r\n\tfor(var i in options)\r\n\t\tthis[i] = options[i];\t\t\r\n\r\n\tif(!options.bounding)\r\n\t\tthis.updateBoundingBox();\r\n}\r\n\r\n/**\r\n* Returns the amount of memory used by this mesh in bytes (sum of all buffers)\r\n* @method getMemory\r\n* @return {number} bytes\r\n*/\r\nMesh.prototype.totalMemory = function()\r\n{\r\n\tvar num = 0|0;\r\n\r\n\tfor (var name in this.vertexBuffers)\r\n\t\tnum += this.vertexBuffers[name].data.buffer.byteLength;\r\n\tfor (var name in this.indexBuffers)\r\n\t\tnum += this.indexBuffers[name].data.buffer.byteLength;\r\n\r\n\treturn num;\r\n}\r\n\r\nMesh.prototype.slice = function(start, length)\r\n{\r\n\tvar new_vertex_buffers = {};\r\n\r\n\tvar indices_buffer = this.indexBuffers[\"triangles\"];\r\n\tif(!indices_buffer)\r\n\t{\r\n\t\tconsole.warn(\"splice in not indexed not supported yet\");\r\n\t\treturn null;\r\n\t}\r\n\r\n\tvar indices = indices_buffer.data;\r\n\r\n\tvar new_triangles = [];\r\n\tvar reindex = new Int32Array( indices.length );\r\n\treindex.fill(-1);\r\n\r\n\tvar end = start + length;\r\n\tif(end >= indices.length)\r\n\t\tend = indices.length;\r\n\r\n\tvar last_index = 0;\r\n\tfor(var j = start; j < end; ++j)\r\n\t{\r\n\t\tvar index = indices[j];\r\n\t\tif( reindex[index] != -1 )\r\n\t\t{\r\n\t\t\tnew_triangles.push(reindex[index]);\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\t//new vertex\r\n\t\tvar new_index = last_index++;\r\n\t\treindex[index] = new_index;\r\n\t\tnew_triangles.push(new_index);\r\n\r\n\t\tfor( var i in this.vertexBuffers )\r\n\t\t{\r\n\t\t\tvar buffer = this.vertexBuffers[i];\r\n\t\t\tvar data = buffer.data;\r\n\t\t\tvar spacing = buffer.spacing;\r\n\t\t\tif(!new_vertex_buffers[i])\r\n\t\t\t\tnew_vertex_buffers[i] = [];\r\n\t\t\tvar new_buffer = new_vertex_buffers[i];\r\n\t\t\tfor(var k = 0; k < spacing; ++k)\r\n\t\t\t\tnew_buffer.push( data[k + index*spacing] );\r\n\t\t}\r\n\t}\r\n\r\n\tvar new_mesh = new GL.Mesh( new_vertex_buffers, {triangles: new_triangles}, null,gl);\r\n\tnew_mesh.updateBoundingBox();\r\n\treturn new_mesh;\r\n}\r\n\r\n\r\n/**\r\n* returns a low poly version of the mesh that takes much less memory (but breaks tiling of uvs and smoothing groups)\r\n* @method simplify\r\n* @return {Mesh} simplified mesh\r\n*/\r\nMesh.prototype.simplify = function()\r\n{\r\n\t//compute bounding box\r\n\tvar bb = this.getBoundingBox();\r\n\tvar min = BBox.getMin( bb );\r\n\tvar halfsize = BBox.getHalfsize( bb );\r\n\tvar range = vec3.scale( vec3.create(), halfsize, 2 );\r\n\r\n\tvar newmesh = new GL.Mesh();\r\n\tvar temp = vec3.create();\r\n\r\n\tfor(var i in this.vertexBuffers)\r\n\t{\r\n\t\t//take every vertex and normalize it to the bounding box\r\n\t\tvar buffer = this.vertexBuffers[i];\r\n\t\tvar data = buffer.data;\r\n\r\n\t\tvar new_data = new Float32Array( data.length );\r\n\r\n\t\tif(i == \"vertices\")\r\n\t\t{\r\n\t\t\tfor(var j = 0, l = data.length; j < l; j+=3 )\r\n\t\t\t{\r\n\t\t\t\tvar v = data.subarray(j,j+3);\r\n\t\t\t\tvec3.sub( temp, v, min );\r\n\t\t\t\tvec3.div( temp, temp, range );\r\n\t\t\t\ttemp[0] = Math.round(temp[0] * 256) / 256;\r\n\t\t\t\ttemp[1] = Math.round(temp[1] * 256) / 256;\r\n\t\t\t\ttemp[2] = Math.round(temp[2] * 256) / 256;\r\n\t\t\t\tvec3.mul( temp, temp, range );\r\n\t\t\t\tvec3.add( temp, temp, min );\r\n\t\t\t\tnew_data.set( temp, j );\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t}\r\n\r\n\t\tnewmesh.addBuffer();\r\n\t}\r\n\r\n\t//search for repeated vertices\r\n\t\t//compute the average normal and coord\r\n\t//reindex the triangles\r\n\t//return simplified mesh\t\r\n}\r\n\r\n/**\r\n* Static method for the class Mesh to create a mesh from a list of common streams\r\n* @method Mesh.load\r\n* @param {Object} buffers object will all the buffers\r\n* @param {Object} options [optional]\r\n* @param {Mesh} output_mesh [optional] mesh to store the mesh, otherwise is created\r\n* @param {WebGLContext} gl [optional] if omitted, the global.gl is used\r\n*/\r\nMesh.load = function( buffers, options, output_mesh, gl ) {\r\n\toptions = options || {};\r\n\tif(options.no_gl)\r\n\t\tgl = null;\r\n\tvar mesh = output_mesh || new GL.Mesh(null,null,null,gl);\r\n\tmesh.configure( buffers, options );\r\n\treturn mesh;\r\n}\r\n\r\n/**\r\n* Returns a mesh with all the meshes merged (you can apply transforms individually to every buffer)\r\n* @method Mesh.mergeMeshes\r\n* @param {Array} meshes array containing object like { mesh:, matrix:, texture_matrix: }\r\n* @param {Object} options { only_data: to get the mesh data without uploading it }\r\n* @return {GL.Mesh|Object} the mesh in GL.Mesh format or Object format (if options.only_data is true)\r\n*/\r\nMesh.mergeMeshes = function( meshes, options )\r\n{\r\n\toptions = options || {};\r\n\r\n\tvar vertex_buffers = {};\r\n\tvar index_buffers = {};\r\n\tvar offsets = {}; //tells how many positions indices must be offseted\r\n\tvar vertex_offsets = [];\r\n\tvar current_vertex_offset = 0;\r\n\tvar groups = [];\r\n\r\n\t//vertex buffers\r\n\t//compute size\r\n\tfor(var i = 0; i < meshes.length; ++i)\r\n\t{\r\n\t\tvar mesh_info = meshes[i];\r\n\t\tvar mesh = mesh_info.mesh;\r\n\t\tvar offset = current_vertex_offset;\r\n\t\tvertex_offsets.push( offset );\r\n\t\tvar length = mesh.vertexBuffers[\"vertices\"].data.length / 3;\r\n\t\tcurrent_vertex_offset += length;\r\n\r\n\t\tfor(var j in mesh.vertexBuffers)\r\n\t\t{\r\n\t\t\tif(!vertex_buffers[j])\r\n\t\t\t\tvertex_buffers[j] = mesh.vertexBuffers[j].data.length;\r\n\t\t\telse\r\n\t\t\t\tvertex_buffers[j] += mesh.vertexBuffers[j].data.length;\r\n\t\t}\r\n\r\n\t\tfor(var j in mesh.indexBuffers)\r\n\t\t{\r\n\t\t\tif(!index_buffers[j])\r\n\t\t\t\tindex_buffers[j] = mesh.indexBuffers[j].data.length;\r\n\t\t\telse\r\n\t\t\t\tindex_buffers[j] += mesh.indexBuffers[j].data.length;\r\n\t\t}\r\n\r\n\t\t//groups\r\n\t\tvar group = {\r\n\t\t\tname: \"mesh_\" + i,\r\n\t\t\tstart: offset,\r\n\t\t\tlength: length,\r\n\t\t\tmaterial: \"\"\r\n\t\t};\r\n\r\n\t\tgroups.push( group );\r\n\t}\r\n\r\n\t//allocate\r\n\tfor(var j in vertex_buffers)\r\n\t{\r\n\t\tvar datatype = options[j];\r\n\t\tif(datatype === null)\r\n\t\t{\r\n\t\t\tdelete vertex_buffers[j];\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\tif(!datatype)\r\n\t\t\tdatatype = Float32Array;\r\n\r\n\t\tvertex_buffers[j] = new datatype( vertex_buffers[j] );\r\n\t\toffsets[j] = 0;\r\n\t}\r\n\r\n\tfor(var j in index_buffers)\r\n\t{\r\n\t\tindex_buffers[j] = new Uint32Array( index_buffers[j] );\r\n\t\toffsets[j] = 0;\r\n\t}\r\n\r\n\t//store\r\n\tfor(var i = 0; i < meshes.length; ++i)\r\n\t{\r\n\t\tvar mesh_info = meshes[i];\r\n\t\tvar mesh = mesh_info.mesh;\r\n\t\tvar offset = 0;\r\n\t\tvar length = 0;\r\n\r\n\t\tfor(var j in mesh.vertexBuffers)\r\n\t\t{\r\n\t\t\tif(!vertex_buffers[j])\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tif(j == \"vertices\")\r\n\t\t\t\tlength = mesh.vertexBuffers[j].data.length / 3;\r\n\r\n\t\t\tvertex_buffers[j].set( mesh.vertexBuffers[j].data, offsets[j] );\r\n\r\n\t\t\t//apply transform\r\n\t\t\tif(mesh_info[ j + \"_matrix\"] )\r\n\t\t\t{\r\n\t\t\t\tvar matrix = mesh_info[ j + \"_matrix\" ];\r\n\t\t\t\tif(matrix.length == 16)\r\n\t\t\t\t\tapply_transform( vertex_buffers[j], offsets[j], mesh.vertexBuffers[j].data.length, matrix )\r\n\t\t\t\telse if(matrix.length == 9)\r\n\t\t\t\t\tapply_transform2D( vertex_buffers[j], offsets[j], mesh.vertexBuffers[j].data.length, matrix )\r\n\t\t\t}\r\n\r\n\t\t\toffsets[j] += mesh.vertexBuffers[j].data.length;\r\n\t\t}\r\n\r\n\t\tfor(var j in mesh.indexBuffers)\r\n\t\t{\r\n\t\t\tindex_buffers[j].set( mesh.indexBuffers[j].data, offsets[j] );\r\n\t\t\tapply_offset( index_buffers[j], offsets[j], mesh.indexBuffers[j].data.length, vertex_offsets[i] );\r\n\t\t\toffsets[j] += mesh.indexBuffers[j].data.length;\r\n\t\t}\r\n\t}\r\n\r\n\t//useful functions\r\n\tfunction apply_transform( array, start, length, matrix )\r\n\t{\r\n\t\tvar l = start + length;\r\n\t\tfor(var i = start; i < l; i+=3)\r\n\t\t{\r\n\t\t\tvar v = array.subarray(i,i+3);\r\n\t\t\tvec3.transformMat4( v, v, matrix );\r\n\t\t}\r\n\t}\r\n\r\n\tfunction apply_transform2D( array, start, length, matrix )\r\n\t{\r\n\t\tvar l = start + length;\r\n\t\tfor(var i = start; i < l; i+=2)\r\n\t\t{\r\n\t\t\tvar v = array.subarray(i,i+2);\r\n\t\t\tvec2.transformMat3( v, v, matrix );\r\n\t\t}\r\n\t}\r\n\r\n\tfunction apply_offset( array, start, length, offset )\r\n\t{\r\n\t\tvar l = start + length;\r\n\t\tfor(var i = start; i < l; ++i)\r\n\t\t\tarray[i] += offset;\r\n\t}\r\n\r\n\tvar extra = { info: { groups: groups } };\r\n\r\n\t//return\r\n\tif( typeof(gl) != \"undefined\" || options.only_data )\r\n\t\treturn new GL.Mesh( vertex_buffers,index_buffers, extra );\r\n\treturn { \r\n\t\tvertexBuffers: vertex_buffers, \r\n\t\tindexBuffers: index_buffers, \r\n\t\tinfo: { groups: groups } \r\n\t};\r\n}\r\n\r\n\r\n\r\n//Here we store all basic mesh parsers (OBJ, STL) and encoders\r\nMesh.parsers = {};\r\nMesh.encoders = {};\r\nMesh.binary_file_formats = {}; //extensions that must be downloaded in binary\r\nMesh.compressors = {}; //used to compress binary meshes\r\nMesh.decompressors = {}; //used to decompress binary meshes\r\n\r\n/**\r\n* Returns am empty mesh and loads a mesh and parses it using the Mesh.parsers, by default only OBJ is supported\r\n* @method Mesh.fromOBJ\r\n* @param {Array} meshes array containing all the meshes\r\n*/\r\nMesh.fromURL = function(url, on_complete, gl, options)\r\n{\r\n\toptions = options || {};\r\n\tgl = gl || global.gl;\r\n\t\r\n\tvar mesh = new GL.Mesh(undefined,undefined,undefined,gl);\r\n\tmesh.ready = false;\r\n\r\n\tvar pos = url.lastIndexOf(\".\");\r\n\tvar extension = url.substr(pos+1).toLowerCase();\r\n\toptions.binary = Mesh.binary_file_formats[ extension ];\r\n\r\n\tHttpRequest( url, null, function(data) {\r\n\t\tmesh.parse( data, extension );\r\n\t\tdelete mesh[\"ready\"];\r\n\t\tif(on_complete)\r\n\t\t\ton_complete.call(mesh,mesh, url);\r\n\t}, function(err){\r\n\t\tif(on_complete)\r\n\t\t\ton_complete(null);\r\n\t}, options );\r\n\treturn mesh;\r\n}\r\n\r\n/**\r\n* given some data an information about the format, it search for a parser in Mesh.parsers and tries to extract the mesh information\r\n* Only obj supported now\r\n* @method parse\r\n* @param {*} data could be string or ArrayBuffer\r\n* @param {String} format parser file format name (p.e. \"obj\")\r\n* @return {?} depending on the parser\r\n*/\r\nMesh.prototype.parse = function( data, format )\r\n{\r\n\tformat = format.toLowerCase();\r\n\tvar parser = GL.Mesh.parsers[ format ];\r\n\tif(parser)\r\n\t\treturn parser.call(null, data, {mesh: this});\r\n\tthrow(\"GL.Mesh.parse: no parser found for format \" + format );\r\n}\r\n\r\n/**\r\n* It returns the mesh data encoded in the format specified\r\n* Only obj supported now\r\n* @method encode\r\n* @param {String} format to encode the data to (p.e. \"obj\")\r\n* @return {?} String with the info\r\n*/\r\nMesh.prototype.encode = function( format, options )\r\n{\r\n\tformat = format.toLowerCase();\r\n\tvar encoder = GL.Mesh.encoders[ format ];\r\n\tif(encoder)\r\n\t\treturn encoder.call(null, this, options );\r\n\tthrow(\"GL.Mesh.encode: no encoder found for format \" + format );\r\n}\r\n\r\n/**\r\n* Returns a shared mesh containing a quad to be used when rendering to the screen\r\n* Reusing the same quad helps not filling the memory\r\n* @method getScreenQuad\r\n* @return {GL.Mesh} the screen quad\r\n*/\r\nMesh.getScreenQuad = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar mesh = gl.meshes[\":screen_quad\"];\r\n\tif(mesh)\r\n\t\treturn mesh;\r\n\r\n\tvar vertices = new Float32Array([0,0,0, 1,1,0, 0,1,0,  0,0,0, 1,0,0, 1,1,0 ]);\r\n\tvar coords = new Float32Array([0,0, 1,1, 0,1,  0,0, 1,0, 1,1 ]);\r\n\tmesh = new GL.Mesh({ vertices: vertices, coords: coords}, undefined, undefined, gl);\r\n\treturn gl.meshes[\":screen_quad\"] = mesh;\r\n}\r\n\r\nfunction linearizeArray( array, typed_array_class )\r\n{\r\n\tif(array.constructor === typed_array_class)\r\n\t\treturn array;\r\n\tif(array.constructor !== Array)\r\n\t{\r\n\t\ttyped_array_class = typed_array_class || Float32Array;\r\n\t\treturn new typed_array_class(array);\r\n\t}\r\n\r\n\ttyped_array_class = typed_array_class || Float32Array;\r\n\tvar components = array[0].length;\r\n\tvar size = array.length * components;\r\n\tvar buffer = new typed_array_class(size);\r\n\r\n\tfor (var i=0; i < array.length;++i)\r\n\t\tfor(var j=0; j < components; ++j)\r\n\t\t\tbuffer[i*components + j] = array[i][j];\r\n\treturn buffer;\r\n}\r\n\r\nGL.linearizeArray = linearizeArray;\r\n\r\n/* BINARY MESHES */\r\n//Add some functions to the classes in LiteGL to allow store in binary\r\nGL.Mesh.EXTENSION = \"wbin\";\r\nGL.Mesh.enable_wbin_compression = true;\r\n\r\n//this is used when a mesh is dynamic and constantly changes\r\nfunction DynamicMesh( size, normals, coords, colors, gl )\r\n{\r\n\tsize = size || 1024;\r\n\r\n\tif(GL.debug)\r\n\t\tconsole.log(\"GL.Mesh created\");\r\n\r\n\tif( gl !== null )\r\n\t{\r\n\t\tgl = gl || global.gl;\r\n\t\tthis.gl = gl;\r\n\t}\r\n\r\n\t//used to avoid problems with resources moving between different webgl context\r\n\tthis._context_id = gl.context_id; \r\n\r\n\tthis.vertexBuffers = {};\r\n\tthis.indexBuffers = {};\r\n\r\n\t//here you can store extra info, like groups, which is an array of { name, start, length, material }\r\n\tthis.info = {\r\n\t\tgroups: []\r\n\t}; \r\n\tthis._bounding = BBox.create(); //here you can store a AABB in BBox format\r\n\r\n\tthis.resize( size );\r\n}\r\n\r\nDynamicMesh.DEFAULT_NORMAL = vec3.fromValues(0,1,0);\r\nDynamicMesh.DEFAULT_COORD = vec2.fromValues(0.5,0.5);\r\nDynamicMesh.DEFAULT_COLOR = vec4.fromValues(1,1,1,1);\r\n\r\nDynamicMesh.prototype.resize = function( size )\r\n{\r\n\tvar buffers = {};\r\n\r\n\tthis._vertex_data = new Float32Array( size * 3 );\r\n\tbuffers.vertices = this._vertex_data;\r\n\r\n\tif( normals )\r\n\t\tbuffers.normals = this._normal_data = new Float32Array( size * 3 );\r\n\tif( coords )\r\n\t\tbuffers.coords = this._coord_data = new Float32Array( size * 2 );\r\n\tif( colors )\r\n\t\tbuffers.colors = this._color_data = new Float32Array( size * 4 );\r\n\r\n\tthis.addBuffers( buffers );\r\n\r\n\tthis.current_pos = 0;\r\n\tthis.max_size = size;\r\n\tthis._must_update = true;\r\n}\r\n\r\nDynamicMesh.prototype.clear = function()\r\n{\r\n\tthis.current_pos = 0;\r\n}\r\n\r\nDynamicMesh.prototype.addPoint = function( vertex, normal, coord, color )\r\n{\r\n\tif (pos >= this.max_size)\r\n\t{\r\n\t\tconsole.warn(\"DynamicMesh: not enough space, reserve more\");\r\n\t\treturn false;\r\n\t}\r\n\tvar pos = this.current_pos++;\r\n\r\n\tthis._vertex_data.set( vertex, pos*3 );\r\n\r\n\tif(this._normal_data)\r\n\t\tthis._normal_data.set( normal || DynamicMesh.DEFAULT_NORMAL, pos*3 );\r\n\tif(this._coord_data)\r\n\t\tthis._coord_data.set( coord || DynamicMesh.DEFAULT_COORD, pos*2 );\r\n\tif(this._color_data)\r\n\t\tthis._color_data.set( color || DynamicMesh.DEFAULT_COLOR, pos*4 );\r\n\r\n\tthis._must_update = true;\r\n\treturn true;\r\n}\r\n\r\nDynamicMesh.prototype.update = function( force )\r\n{\r\n\tif(!this._must_update && !force)\r\n\t\treturn this.current_pos;\r\n\tthis._must_update = false;\r\n\r\n\tthis.getBuffer(\"vertices\").upload( gl.STREAM_DRAW );\r\n\tif(this._normal_data)\r\n\t\tthis.getBuffer(\"normal\").upload( gl.STREAM_DRAW );\r\n\tif(this._coord_data)\r\n\t\tthis.getBuffer(\"coord\").upload( gl.STREAM_DRAW );\r\n\tif(this._color_data)\r\n\t\tthis.getBuffer(\"color\").upload( gl.STREAM_DRAW );\r\n\treturn this.current_pos;\r\n}\r\n\r\nextendClass( DynamicMesh, Mesh );\n\r\n/**\r\n* @class Mesh\r\n*/\r\n\r\n/**\r\n* Returns a planar mesh (you can choose how many subdivisions)\r\n* @method Mesh.plane\r\n* @param {Object} options valid options: detail, detailX, detailY, size, width, heigth, xz (horizontal plane)\r\n*/\r\nMesh.plane = function(options, gl) {\r\n\toptions = options || {};\r\n\toptions.triangles = [];\r\n\tvar mesh = {};\r\n\tvar detailX = options.detailX || options.detail || 1;\r\n\tvar detailY = options.detailY || options.detail || 1;\r\n\tvar width = options.width || options.size || 1;\r\n\tvar height = options.height || options.size || 1;\r\n\tvar xz = options.xz;\r\n\twidth *= 0.5;\r\n\theight *= 0.5;\r\n\r\n\tvar triangles = [];\r\n\tvar vertices = [];\r\n\tvar coords = [];\r\n\tvar normals = [];\r\n\r\n\tvar N = vec3.fromValues(0,0,1);\r\n\tif(xz) \r\n\t\tN.set([0,1,0]);\r\n\r\n\tfor (var y = 0; y <= detailY; y++) {\r\n\t\tvar t = y / detailY;\r\n\t\tfor (var x = 0; x <= detailX; x++) {\r\n\t\t  var s = x / detailX;\r\n\t\t  if(xz)\r\n\t\t\t  vertices.push((2 * s - 1) * width, 0, -(2 * t - 1) * height);\r\n\t\t  else\r\n\t\t\t  vertices.push((2 * s - 1) * width, (2 * t - 1) * height, 0);\r\n\t\t  coords.push(s, t);\r\n\t\t  normals.push(N[0],N[1],N[2]);\r\n\t\t  if (x < detailX && y < detailY) {\r\n\t\t\tvar i = x + y * (detailX + 1);\r\n\t\t\tif(xz) //horizontal\r\n\t\t\t{\r\n\t\t\t\ttriangles.push(i + 1, i + detailX + 1, i);\r\n\t\t\t\ttriangles.push(i + 1, i + detailX + 2, i + detailX + 1);\r\n\t\t\t}\r\n\t\t\telse //vertical\r\n\t\t\t{\r\n\t\t\t\ttriangles.push(i, i + 1, i + detailX + 1);\r\n\t\t\t\ttriangles.push(i + detailX + 1, i + 1, i + detailX + 2);\r\n\t\t\t}\r\n\t\t  }\r\n\t\t}\r\n\t}\r\n\r\n\tvar bounding = BBox.fromCenterHalfsize( [0,0,0], xz ? [width,0,height] : [width,height,0] );\r\n\tvar mesh_info = {vertices:vertices, normals: normals, coords: coords, triangles: triangles };\r\n\treturn GL.Mesh.load( mesh_info, { bounding: bounding }, gl);\r\n};\r\n\r\n/**\r\n* Returns a 2D Mesh (be careful, stream is vertices2D, used for 2D engines )\r\n* @method Mesh.plane2D\r\n*/\r\nMesh.plane2D = function(options, gl) {\r\n\tvar vertices = new Float32Array([-1,1, 1,-1, 1,1, -1,1, -1,-1, 1,-1]);\r\n\tvar coords = new Float32Array([0,1, 1,0, 1,1, 0,1, 0,0, 1,0]);\r\n\r\n\tif(options && options.size)\r\n\t{\r\n\t\tvar s = options.size * 0.5;\r\n\t\tfor(var i = 0; i < vertices.length; ++i)\r\n\t\t\tvertices[i] *= s;\r\n\t}\r\n\treturn new GL.Mesh( {vertices2D: vertices, coords: coords },null,gl );\r\n};\r\n\r\n/**\r\n* Returns a point mesh \r\n* @method Mesh.point\r\n* @param {Object} options no options\r\n*/\r\nMesh.point = function(options) {\r\n\treturn new GL.Mesh( {vertices: [0,0,0]} );\r\n}\r\n\r\n/**\r\n* Returns a cube mesh \r\n* @method Mesh.cube\r\n* @param {Object} options valid options: size \r\n*/\r\nMesh.cube = function(options, gl) {\r\n\toptions = options || {};\r\n\tvar halfsize = (options.size || 1) * 0.5;\r\n\r\n\tvar buffers = {};\r\n\t//[[-1,1,-1],[-1,-1,+1],[-1,1,1],[-1,1,-1],[-1,-1,-1],[-1,-1,+1],[1,1,-1],[1,1,1],[1,-1,+1],[1,1,-1],[1,-1,+1],[1,-1,-1],[-1,1,1],[1,-1,1],[1,1,1],[-1,1,1],[-1,-1,1],[1,-1,1],[-1,1,-1],[1,1,-1],[1,-1,-1],[-1,1,-1],[1,-1,-1],[-1,-1,-1],[-1,1,-1],[1,1,1],[1,1,-1],[-1,1,-1],[-1,1,1],[1,1,1],[-1,-1,-1],[1,-1,-1],[1,-1,1],[-1,-1,-1],[1,-1,1],[-1,-1,1]]\r\n\tbuffers.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]);\r\n\tfor(var i = 0, l = buffers.vertices.length; i < l; ++i)\r\n\t\tbuffers.vertices[i] *= halfsize;\r\n\r\n\t//[[-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]]\r\n\t//[[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]];\r\n\tbuffers.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]);\r\n\tbuffers.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]);\r\n\r\n\tif(options.wireframe)\r\n\t\tbuffers.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  ]);\r\n\toptions.bounding = BBox.fromCenterHalfsize( [0,0,0], [halfsize,halfsize,halfsize] );\r\n\treturn GL.Mesh.load(buffers, options, gl);\r\n}\r\n\r\n\r\n/**\r\n* Returns a cube mesh of a given size\r\n* @method Mesh.cube\r\n* @param {Object} options valid options: size, sizex, sizey, sizez\r\n*/\r\nMesh.box = function(options, gl) {\r\n\toptions = options || {};\r\n\tvar sizex = options.sizex || 1;\r\n\tvar sizey = options.sizey || 1;\r\n\tvar sizez = options.sizez || 1;\r\n\tsizex *= 0.5;\r\n\tsizey *= 0.5;\r\n\tsizez *= 0.5;\r\n\r\n\tvar buffers = {};\r\n\t//[[-1,1,-1],[-1,-1,+1],[-1,1,1],[-1,1,-1],[-1,-1,-1],[-1,-1,+1],[1,1,-1],[1,1,1],[1,-1,+1],[1,1,-1],[1,-1,+1],[1,-1,-1],[-1,1,1],[1,-1,1],[1,1,1],[-1,1,1],[-1,-1,1],[1,-1,1],[-1,1,-1],[1,1,-1],[1,-1,-1],[-1,1,-1],[1,-1,-1],[-1,-1,-1],[-1,1,-1],[1,1,1],[1,1,-1],[-1,1,-1],[-1,1,1],[1,1,1],[-1,-1,-1],[1,-1,-1],[1,-1,1],[-1,-1,-1],[1,-1,1],[-1,-1,1]]\r\n\tbuffers.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]);\r\n\t//for(var i in options.vertices) for(var j in options.vertices[i]) options.vertices[i][j] *= size;\r\n\tfor(var i = 0, l = buffers.vertices.length; i < l; i+=3) \r\n\t{\r\n\t\tbuffers.vertices[i] *= sizex;\r\n\t\tbuffers.vertices[i+1] *= sizey;\r\n\t\tbuffers.vertices[i+2] *= sizez;\r\n\t}\r\n\r\n\t//[[-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]]\r\n\t//[[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]];\r\n\tbuffers.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]);\r\n\tbuffers.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]);\r\n\r\n\tif(options.wireframe)\r\n\t\tbuffers.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  ]);\r\n\r\n\toptions.bounding = BBox.fromCenterHalfsize( [0,0,0], [sizex,sizey,sizez] );\r\n\r\n\treturn GL.Mesh.load(buffers, options, gl);\r\n}\r\n\r\n/**\r\n* Returns a circle mesh \r\n* @method Mesh.circle\r\n* @param {Object} options valid options: size,radius, xz = in xz plane, otherwise xy plane\r\n*/\r\nMesh.circle = function( options, gl ) {\r\n\toptions = options || {};\r\n\tvar size = options.size || options.radius || 1;\r\n\tvar slices = Math.ceil(options.slices || 24);\r\n\tvar xz = options.xz || false;\r\n\tvar empty = options.empty || false;\r\n\tif(slices < 3) slices = 3;\r\n\tvar delta = (2 * Math.PI) / slices;\r\n\r\n\tvar center = vec3.create();\r\n\tvar A = vec3.create();\r\n\tvar N = vec3.fromValues(0,0,1);\r\n\tvar uv_center = vec2.fromValues(0.5,0.5);\r\n\tvar uv = vec2.create();\r\n\r\n\tif(xz) N.set([0,1,0]);\r\n\r\n\tvar index = xz ? 2 : 1;\r\n\r\n\tvar vertices = new Float32Array(3 * (slices + 1));\r\n\tvar normals = new Float32Array(3 * (slices + 1));\r\n\tvar coords = new Float32Array(2 * (slices + 1));\r\n\tvar triangles = null;\r\n\r\n\t//the center is always the same\r\n\tvertices.set(center, 0);\r\n\tnormals.set(N, 0);\r\n\tcoords.set(uv_center, 0);\r\n\r\n\tvar sin = 0;\r\n\tvar cos = 0;\r\n\r\n\t//compute vertices\r\n\tfor(var i = 0; i < slices; ++i )\r\n\t{\r\n\t\tsin = Math.sin( delta * i );\r\n\t\tcos = Math.cos( delta * i );\r\n\r\n\t\tA[0] = sin * size;\r\n\t\tA[index] = cos * size;\r\n\t\tuv[0] = sin * 0.5 + 0.5;\r\n\t\tuv[1] = cos * 0.5 + 0.5;\r\n\t\tvertices.set(A, i * 3 + 3);\r\n\t\tnormals.set(N, i * 3 + 3);\r\n\t\tcoords.set(uv, i * 2 + 2);\r\n\t}\r\n\r\n\tif(empty)\r\n\t{\r\n\t\tvertices = vertices.subarray(3);\r\n\t\tnormals = vertices.subarray(3);\r\n\t\tcoords = vertices.subarray(2);\r\n\t\ttriangles = null;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar triangles = new Uint16Array(3 * slices);\r\n\t\tvar offset = 2;\r\n\t\tvar offset2 = 1;\r\n\t\tif(xz)\r\n\t\t{\r\n\t\t\toffset = 1;\r\n\t\t\toffset2 = 2;\r\n\t\t}\r\n\r\n\t\t//compute indices\r\n\t\tfor(var i = 0; i < slices-1; ++i )\r\n\t\t{\r\n\t\t\ttriangles[i*3] = 0;\r\n\t\t\ttriangles[i*3+1] = i+offset;\r\n\t\t\ttriangles[i*3+2] = i+offset2;\r\n\t\t}\r\n\r\n\t\ttriangles[i*3] = 0;\r\n\t\tif(xz)\r\n\t\t{\r\n\t\t\ttriangles[i*3+1] = i+1;\r\n\t\t\ttriangles[i*3+2] = 1;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\ttriangles[i*3+1] = 1;\r\n\t\t\ttriangles[i*3+2] = i+1;\r\n\t\t}\r\n\t}\r\n\r\n\toptions.bounding = BBox.fromCenterHalfsize( [0,0,0], xz ? [size,0,size] : [size,size,0] );\r\n\r\n\tvar buffers = {vertices: vertices, normals: normals, coords: coords, triangles: triangles};\r\n\r\n\tif(options.wireframe)\r\n\t{\r\n\t\tvar wireframe = new Uint16Array(slices*2);\r\n\t\tfor(var i = 0; i < slices; i++)\r\n\t\t{\r\n\t\t\twireframe[i*2] = i;\r\n\t\t\twireframe[i*2+1] = i+1;\r\n\t\t}\r\n\t\twireframe[0] = slices;\r\n\t\tbuffers.wireframe = wireframe;\r\n\t}\r\n\r\n\treturn GL.Mesh.load( buffers, options, gl );\r\n}\r\n\r\n/**\r\n* Returns a cube mesh \r\n* @method Mesh.cylinder\r\n* @param {Object} options valid options: radius, height, subdivisions \r\n*/\r\nMesh.cylinder = function( options, gl ) {\r\n\toptions = options || {};\r\n\tvar radius = options.radius || options.size || 1;\r\n\tvar height = options.height || options.size || 2;\r\n\tvar subdivisions = options.subdivisions || 64;\r\n\r\n\tvar vertices = new Float32Array(subdivisions * 6 * 3 * 2 );\r\n\tvar normals = new Float32Array(subdivisions * 6 * 3 * 2 );\r\n\tvar coords = new Float32Array(subdivisions * 6 * 2 * 2 );\r\n\t//not indexed because caps have different normals and uvs so...\r\n\r\n\tvar delta = 2*Math.PI / subdivisions;\r\n\tvar normal = null;\r\n\tfor(var i = 0; i < subdivisions; ++i)\r\n\t{\r\n\t\tvar angle = i * delta;\r\n\r\n\t\tnormal = [ Math.sin(angle), 0, Math.cos(angle)];\r\n\t\tvertices.set([ normal[0]*radius, height*0.5, normal[2]*radius], i*6*3);\r\n\t\tnormals.set(normal, i*6*3 );\r\n\t\tcoords.set([i/subdivisions,1], i*6*2 );\r\n\r\n\t\tnormal = [ Math.sin(angle), 0, Math.cos(angle)];\r\n\t\tvertices.set([ normal[0]*radius, height*-0.5, normal[2]*radius], i*6*3 + 3);\r\n\t\tnormals.set(normal, i*6*3 + 3);\r\n\t\tcoords.set([i/subdivisions,0], i*6*2 + 2);\r\n\r\n\t\tnormal = [ Math.sin(angle+delta), 0, Math.cos(angle+delta)];\r\n\t\tvertices.set([ normal[0]*radius, height*-0.5, normal[2]*radius], i*6*3 + 6);\r\n\t\tnormals.set(normal, i*6*3 + 6);\r\n\t\tcoords.set([(i+1)/subdivisions,0], i*6*2 + 4);\r\n\r\n\t\tnormal = [ Math.sin(angle+delta), 0, Math.cos(angle+delta)];\r\n\t\tvertices.set([ normal[0]*radius, height*0.5, normal[2]*radius], i*6*3 + 9);\r\n\t\tnormals.set(normal, i*6*3 + 9);\r\n\t\tcoords.set([(i+1)/subdivisions,1], i*6*2 + 6);\r\n\r\n\t\tnormal = [ Math.sin(angle), 0, Math.cos(angle)];\r\n\t\tvertices.set([ normal[0]*radius, height*0.5, normal[2]*radius], i*6*3 + 12);\r\n\t\tnormals.set(normal, i*6*3 + 12);\r\n\t\tcoords.set([i/subdivisions,1], i*6*2 + 8);\r\n\r\n\t\tnormal = [ Math.sin(angle+delta), 0, Math.cos(angle+delta)];\r\n\t\tvertices.set([ normal[0]*radius, height*-0.5, normal[2]*radius], i*6*3 + 15);\r\n\t\tnormals.set(normal, i*6*3 + 15);\r\n\t\tcoords.set([(i+1)/subdivisions,0], i*6*2 + 10);\r\n\t}\r\n\r\n\tvar pos = i*6*3;\r\n\tvar pos_uv = i*6*2;\r\n\tvar caps_start = pos;\r\n\r\n\t//caps\r\n\tif( options.caps === false )\r\n\t{\r\n\t\t//finalize arrays\r\n\t\tvertices = vertices.subarray(0,pos);\r\n\t\tnormals = normals.subarray(0,pos);\r\n\t\tcoords = coords.subarray(0,pos_uv);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar top_center = vec3.fromValues(0,height*0.5,0);\r\n\t\tvar bottom_center = vec3.fromValues(0,height*-0.5,0);\r\n\t\tvar up = vec3.fromValues(0,1,0);\r\n\t\tvar down = vec3.fromValues(0,-1,0);\r\n\t\tfor(var i = 0; i < subdivisions; ++i)\r\n\t\t{\r\n\t\t\tvar angle = i * delta;\r\n\r\n\t\t\tvar uv = vec3.fromValues( Math.sin(angle), 0, Math.cos(angle) );\r\n\t\t\tvar uv2 = vec3.fromValues( Math.sin(angle+delta), 0, Math.cos(angle+delta) );\r\n\r\n\t\t\tvertices.set([ uv[0]*radius, height*0.5, uv[2]*radius], pos + i*6*3);\r\n\t\t\tnormals.set(up, pos + i*6*3 );\r\n\t\t\tcoords.set( [ -uv[0] * 0.5 + 0.5,uv[2] * 0.5 + 0.5], pos_uv + i*6*2 );\r\n\r\n\t\t\tvertices.set([ uv2[0]*radius, height*0.5, uv2[2]*radius], pos + i*6*3 + 3);\r\n\t\t\tnormals.set(up, pos + i*6*3 + 3 );\r\n\t\t\tcoords.set( [ -uv2[0] * 0.5 + 0.5,uv2[2] * 0.5 + 0.5], pos_uv + i*6*2 + 2 );\r\n\r\n\t\t\tvertices.set( top_center, pos + i*6*3 + 6 );\r\n\t\t\tnormals.set(up, pos + i*6*3 + 6);\r\n\t\t\tcoords.set([0.5,0.5], pos_uv + i*6*2 + 4);\r\n\t\t\t\r\n\t\t\t//bottom\r\n\t\t\tvertices.set([ uv2[0]*radius, height*-0.5, uv2[2]*radius], pos + i*6*3 + 9);\r\n\t\t\tnormals.set(down, pos + i*6*3 + 9);\r\n\t\t\tcoords.set( [ uv2[0] * 0.5 + 0.5,uv2[2] * 0.5 + 0.5], pos_uv + i*6*2 + 6);\r\n\r\n\t\t\tvertices.set([ uv[0]*radius, height*-0.5, uv[2]*radius], pos + i*6*3 + 12);\r\n\t\t\tnormals.set(down, pos + i*6*3 + 12 );\r\n\t\t\tcoords.set( [ uv[0] * 0.5 + 0.5,uv[2] * 0.5 + 0.5], pos_uv + i*6*2 + 8 );\r\n\r\n\t\t\tvertices.set( bottom_center, pos + i*6*3 + 15 );\r\n\t\t\tnormals.set( down, pos + i*6*3 + 15);\r\n\t\t\tcoords.set( [0.5,0.5], pos_uv + i*6*2 + 10);\r\n\t\t}\r\n\t}\r\n\r\n\tvar buffers = {\r\n\t\tvertices: vertices,\r\n\t\tnormals: normals,\r\n\t\tcoords: coords\r\n\t}\r\n\toptions.bounding = BBox.fromCenterHalfsize( [0,0,0], [radius,height*0.5,radius] );\r\n\toptions.info = { groups: [] };\r\n\r\n\tif(options.caps !== false)\r\n\t{\r\n\t\toptions.info.groups.push({ name:\"side\", start: 0, length: caps_start / 3});\r\n\t\toptions.info.groups.push({ name:\"caps\", start: caps_start / 3, length: (vertices.length - caps_start) / 3});\r\n\t}\r\n\r\n\treturn Mesh.load( buffers, options, gl );\r\n}\r\n\r\n/**\r\n* Returns a cone mesh \r\n* @method Mesh.cone\r\n* @param {Object} options valid options: radius, height, subdivisions \r\n*/\r\nMesh.cone = function( options, gl ) {\r\n\toptions = options || {};\r\n\tvar radius = options.radius || options.size || 1;\r\n\tvar height = options.height || options.size || 2;\r\n\tvar subdivisions = options.subdivisions || 64;\r\n\r\n\tvar vertices = new Float32Array(subdivisions * 3 * 3 * 2);\r\n\tvar normals = new Float32Array(subdivisions * 3 * 3 * 2);\r\n\tvar coords = new Float32Array(subdivisions * 2 * 3 * 2);\r\n\t//not indexed because caps have different normals and uvs so...\r\n\r\n\tvar delta = 2*Math.PI / subdivisions;\r\n\tvar normal = null;\r\n\tvar normal_y = radius / height;\r\n\tvar up = [0,1,0];\r\n\r\n\tfor(var i = 0; i < subdivisions; ++i)\r\n\t{\r\n\t\tvar angle = i * delta;\r\n\r\n\t\tnormal = [ Math.sin(angle+delta*0.5), normal_y, Math.cos(angle+delta*0.5)];\r\n\t\tvec3.normalize(normal,normal);\r\n\t\t//normal = up;\r\n\t\tvertices.set([ 0, height, 0] , i*6*3);\r\n\t\tnormals.set(normal, i*6*3 );\r\n\t\tcoords.set([i/subdivisions,1], i*6*2 );\r\n\r\n\t\tnormal = [ Math.sin(angle), normal_y, Math.cos(angle)];\r\n\t\tvertices.set([ normal[0]*radius, 0, normal[2]*radius], i*6*3 + 3);\r\n\t\tvec3.normalize(normal,normal);\r\n\t\tnormals.set(normal, i*6*3 + 3);\r\n\t\tcoords.set([i/subdivisions,0], i*6*2 + 2);\r\n\r\n\t\tnormal = [ Math.sin(angle+delta), normal_y, Math.cos(angle+delta)];\r\n\t\tvertices.set([ normal[0]*radius, 0, normal[2]*radius], i*6*3 + 6);\r\n\t\tvec3.normalize(normal,normal);\r\n\t\tnormals.set(normal, i*6*3 + 6);\r\n\t\tcoords.set([(i+1)/subdivisions,0], i*6*2 + 4);\r\n\t}\r\n\r\n\tvar pos = 0;//i*3*3;\r\n\tvar pos_uv = 0;//i*3*2;\r\n\r\n\t//cap\r\n\tvar bottom_center = vec3.fromValues(0,0,0);\r\n\tvar down = vec3.fromValues(0,-1,0);\r\n\tfor(var i = 0; i < subdivisions; ++i)\r\n\t{\r\n\t\tvar angle = i * delta;\r\n\r\n\t\tvar uv = vec3.fromValues( Math.sin(angle), 0, Math.cos(angle) );\r\n\t\tvar uv2 = vec3.fromValues( Math.sin(angle+delta), 0, Math.cos(angle+delta) );\r\n\r\n\t\t//bottom\r\n\t\tvertices.set([ uv2[0]*radius, 0, uv2[2]*radius], pos + i*6*3 + 9);\r\n\t\tnormals.set(down, pos + i*6*3 + 9);\r\n\t\tcoords.set( [ uv2[0] * 0.5 + 0.5,uv2[2] * 0.5 + 0.5], pos_uv + i*6*2 + 6);\r\n\r\n\t\tvertices.set([ uv[0]*radius, 0, uv[2]*radius], pos + i*6*3 + 12);\r\n\t\tnormals.set(down, pos + i*6*3 + 12 );\r\n\t\tcoords.set( [ uv[0] * 0.5 + 0.5,uv[2] * 0.5 + 0.5], pos_uv + i*6*2 + 8 );\r\n\r\n\t\tvertices.set( bottom_center, pos + i*6*3 + 15 );\r\n\t\tnormals.set( down, pos + i*6*3 + 15);\r\n\t\tcoords.set( [0.5,0.5], pos_uv + i*6*2 + 10);\r\n\t}\r\n\r\n\tvar buffers = {\r\n\t\tvertices: vertices,\r\n\t\tnormals: normals,\r\n\t\tcoords: coords\r\n\t}\r\n\toptions.bounding = BBox.fromCenterHalfsize( [0,height*0.5,0], [radius,height*0.5,radius] );\r\n\r\n\treturn Mesh.load( buffers, options, gl );\r\n}\r\n\r\n/**\r\n* Returns a sphere mesh \r\n* @method Mesh.sphere\r\n* @param {Object} options valid options: radius, lat, long, subdivisions, hemi\r\n*/\r\nMesh.sphere = function( options, gl ) {\r\n\toptions = options || {};\r\n\tvar radius = options.radius || options.size || 1;\r\n\tvar latitudeBands = options.lat || options.subdivisions || 16;\r\n\tvar longitudeBands = options[\"long\"] || options.subdivisions || 16;\r\n\r\n\tvar vertexPositionData = new Float32Array( (latitudeBands+1)*(longitudeBands+1)*3 );\r\n\tvar normalData = new Float32Array( (latitudeBands+1)*(longitudeBands+1)*3 );\r\n\tvar textureCoordData = new Float32Array( (latitudeBands+1)*(longitudeBands+1)*2 );\r\n\tvar indexData = new Uint16Array( latitudeBands*longitudeBands*6 );\r\n\tvar latRange = options.hemi ? Math.PI * 0.5 : Math.PI;\r\n\r\n\tvar i = 0, iuv = 0;\r\n\tfor (var latNumber = 0; latNumber <= latitudeBands; latNumber++)\r\n\t{\r\n\t\tvar theta = latNumber * latRange / latitudeBands;\r\n\t\tvar sinTheta = Math.sin(theta);\r\n\t\tvar cosTheta = Math.cos(theta);\r\n\r\n\t\tfor (var longNumber = 0; longNumber <= longitudeBands; longNumber++)\r\n\t\t{\r\n\t\t\tvar phi = longNumber * 2 * Math.PI / longitudeBands;\r\n\t\t\tvar sinPhi = Math.sin(phi);\r\n\t\t\tvar cosPhi = Math.cos(phi);\r\n\r\n\t\t\tvar x = cosPhi * sinTheta;\r\n\t\t\tvar y = cosTheta;\r\n\t\t\tvar z = sinPhi * sinTheta;\r\n\t\t\tvar u = 1- (longNumber / longitudeBands);\r\n\t\t\tvar v = (1 - latNumber / latitudeBands);\r\n\r\n\t\t\tvertexPositionData.set([radius * x,radius * y,radius * z],i);\r\n\t\t\tnormalData.set([x,y,z],i);\r\n\t\t\ttextureCoordData.set([u,v], iuv );\r\n\t\t\ti += 3;\r\n\t\t\tiuv += 2;\r\n\t\t}\r\n\t}\r\n\r\n\ti=0;\r\n\tfor (var latNumber = 0; latNumber < latitudeBands; latNumber++)\r\n\t{\r\n\t\tfor (var longNumber = 0; longNumber < longitudeBands; longNumber++)\r\n\t\t{\r\n\t\t\tvar first = (latNumber * (longitudeBands + 1)) + longNumber;\r\n\t\t\tvar second = first + longitudeBands + 1;\r\n\r\n\t\t\tindexData.set([second,first,first + 1], i);\r\n\t\t\tindexData.set([second + 1,second,first + 1], i+3);\r\n\t\t\ti += 6;\r\n\t\t}\r\n\t}\r\n\r\n\tvar buffers = {\r\n\t\tvertices: vertexPositionData,\r\n\t\tnormals: normalData,\r\n\t\tcoords: textureCoordData,\r\n\t\ttriangles: indexData\r\n\t};\r\n\r\n\tif(options.wireframe)\r\n\t{\r\n\t\tvar wireframe = new Uint16Array(longitudeBands*latitudeBands*4);\r\n\t\tvar pos = 0;\r\n\t\tfor(var i = 0; i < latitudeBands; i++)\r\n\t\t{\r\n\t\t\tfor(var j = 0; j < longitudeBands; j++)\r\n\t\t\t{\r\n\t\t\t\twireframe[pos] = i*(longitudeBands+1) + j;\r\n\t\t\t\twireframe[pos + 1] = i*(longitudeBands+1) + j + 1;\r\n\t\t\t\tpos += 2;\r\n\t\t\t}\r\n\t\t\twireframe[pos - longitudeBands*2] = i*(longitudeBands+1) + j;\r\n\t\t}\r\n\r\n\t\tfor(var i = 0; i < longitudeBands; i++)\r\n\t\tfor(var j = 0; j < latitudeBands; j++)\r\n\t\t{\r\n\t\t\twireframe[pos] = j*(longitudeBands+1) + i;\r\n\t\t\twireframe[pos + 1] = (j+1)*(longitudeBands+1) + i;\r\n\t\t\tpos += 2;\r\n\t\t}\r\n\t\tbuffers.wireframe = wireframe;\r\n\t}\r\n\r\n\tif(options.hemi)\r\n\t\toptions.bounding = BBox.fromCenterHalfsize( [0,radius*0.5,0], [radius,radius*0.5,radius], radius );\r\n\telse\r\n\t\toptions.bounding = BBox.fromCenterHalfsize( [0,0,0], [radius,radius,radius], radius );\r\n\treturn GL.Mesh.load( buffers, options, gl );\r\n}\r\n\r\n/**\r\n* Returns a grid mesh (must be rendered using gl.LINES)\r\n* @method Mesh.grid\r\n* @param {Object} options valid options: size, lines\r\n*/\r\nMesh.grid = function( options, gl )\r\n{\r\n\toptions = options || {};\r\n\tvar num_lines = options.lines || 11;\r\n\tif(num_lines < 0) \r\n\t\tnum_lines = 1;\r\n\tvar size = options.size || 10;\r\n\r\n\tvar vertexPositionData = new Float32Array( num_lines*2*2*3 );\r\n\tvar hsize = size * 0.5;\r\n\tvar pos = 0;\r\n\tvar x = -hsize;\r\n\tvar delta = size / (num_lines-1);\r\n\r\n\tfor(var i = 0; i < num_lines; i++)\r\n\t{\r\n\t\tvertexPositionData[ pos ] = x;\r\n\t\tvertexPositionData[ pos+2 ] = -hsize;\r\n\t\tvertexPositionData[ pos+3 ] = x;\r\n\t\tvertexPositionData[ pos+5 ] = hsize;\r\n\r\n\t\tvertexPositionData[ pos+6 ] = hsize;\r\n\t\tvertexPositionData[ pos+8 ] = x\r\n\t\tvertexPositionData[ pos+9 ] = -hsize;\r\n\t\tvertexPositionData[ pos+11 ] = x\r\n\r\n\t\tx += delta;\r\n\t\tpos += 12;\r\n\t}\r\n\r\n\treturn new GL.Mesh({vertices: vertexPositionData}, options, gl );\r\n}\r\n\r\n\r\n/**\r\n* Returns a icosahedron mesh (useful to create spheres by subdivision)\r\n* @method Mesh.icosahedron\r\n* @param {Object} options valid options: radius, subdivisions (max: 6)\r\n*/\r\nMesh.icosahedron = function( options, gl ) {\r\n\toptions = options || {};\r\n\tvar radius = options.radius || options.size || 1;\r\n\tvar subdivisions = options.subdivisions === undefined ? 0 : options.subdivisions;\r\n\tif(subdivisions > 6) //dangerous\r\n\t\tsubdivisions = 6;\r\n\r\n\tvar t = (1.0 + Math.sqrt(5)) / 2.0;\r\n\tvar vertices = [-1,t,0, 1,t,0, -1,-t,0, 1,-t,0,\r\n\t\t\t\t\t0,-1,t, 0,1,t, 0,-1,-t, 0,1,-t,\r\n\t\t\t\t\tt,0,-1, t,0,1, -t,0,-1, -t,0,1];\r\n\tvar normals = [];\r\n\tvar coords = [];\r\n\tvar 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 ];\r\n\r\n\t//normalize\r\n\tvar l = vertices.length;\r\n\tfor(var i = 0; i < l; i+=3)\r\n\t{\r\n\t\tvar mod = Math.sqrt( vertices[i]*vertices[i] + vertices[i+1]*vertices[i+1] + vertices[i+2]*vertices[i+2] );\r\n\t\tvar normalx = vertices[i] / mod;\r\n\t\tvar normaly = vertices[i+1] / mod;\r\n\t\tvar normalz = vertices[i+2] / mod;\r\n\t\tnormals.push( normalx, normaly, normalz );\r\n\t\tcoords.push( Math.atan2( normalx, normalz ), Math.acos( normaly ) );\r\n\t\tvertices[i] *= radius/mod;\r\n\t\tvertices[i+1] *= radius/mod;\r\n\t\tvertices[i+2] *= radius/mod;\r\n\t}\r\n\r\n\tvar middles = {};\r\n\r\n\t//A,B = index of vertex in vertex array\r\n\tfunction middlePoint( A, B )\r\n\t{\r\n\t\tvar key = indices[A] < indices[B] ? indices[A] + \":\"+indices[B] : indices[B]+\":\"+indices[A];\r\n\t\tvar r = middles[key];\r\n\t\tif(r)\r\n\t\t\treturn r;\r\n\t\tvar index = vertices.length / 3;\r\n\t\tvertices.push(( vertices[ indices[A]*3] + vertices[ indices[B]*3   ]) * 0.5,\r\n\t\t\t\t\t(vertices[ indices[A]*3+1] + vertices[ indices[B]*3+1 ]) * 0.5,\r\n\t\t\t\t\t(vertices[ indices[A]*3+2] + vertices[ indices[B]*3+2 ]) * 0.5);\r\n\r\n\t\tvar 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] );\r\n\t\tvar normalx = vertices[index*3] / mod;\r\n\t\tvar normaly = vertices[index*3+1] / mod;\r\n\t\tvar normalz = vertices[index*3+2] / mod;\r\n\t\tnormals.push( normalx, normaly, normalz );\r\n\t\tcoords.push( (Math.atan2( normalx, normalz ) / Math.PI) * 0.5, (Math.acos( normaly ) / Math.PI) );\r\n\t\tvertices[index*3] *= radius/mod;\r\n\t\tvertices[index*3+1] *= radius/mod;\r\n\t\tvertices[index*3+2] *= radius/mod;\r\n\r\n\t\tmiddles[key] = index;\r\n\t\treturn index;\r\n\t}\r\n\r\n\tfor (var iR = 0; iR < subdivisions; ++iR )\r\n\t{\r\n\t\tvar new_indices = [];\r\n\t\tvar l = indices.length;\r\n\t\tfor(var i = 0; i < l; i+=3)\r\n\t\t{\r\n\t\t\tvar MA = middlePoint( i, i+1 );\r\n\t\t\tvar MB = middlePoint( i+1, i+2);\r\n\t\t\tvar MC = middlePoint( i+2, i);\r\n\t\t\tnew_indices.push(indices[i], MA, MC);\r\n\t\t\tnew_indices.push(indices[i+1], MB, MA);\r\n\t\t\tnew_indices.push(indices[i+2], MC, MB);\r\n\t\t\tnew_indices.push(MA, MB, MC);\r\n\t\t}\r\n\t\tindices = new_indices;\r\n\t}\r\n\r\n\toptions.bounding = BBox.fromCenterHalfsize( [0,0,0], [radius,radius,radius], radius );\r\n\r\n\treturn new GL.Mesh.load({vertices: vertices, coords: coords, normals: normals, triangles: indices},options,gl);\r\n}\n/**\r\n* @namespace GL\r\n*/\r\n\r\n/**\r\n* 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 <br/>\r\n\tThere is a list of options <br/>\r\n\t========================== <br/>\r\n\t- texture_type: gl.TEXTURE_2D, gl.TEXTURE_CUBE_MAP, default gl.TEXTURE_2D <br/>\r\n\t- format: gl.RGB, gl.RGBA, gl.DEPTH_COMPONENT, default gl.RGBA <br/>\r\n\t- type: gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT, gl.HALF_FLOAT_OES, gl.FLOAT, default gl.UNSIGNED_BYTE <br/>\r\n\t- filter: filtering for mag and min: gl.NEAREST or gl.LINEAR, default gl.NEAREST <br/>\r\n\t- magFilter: magnifying filter: gl.NEAREST, gl.LINEAR, default gl.NEAREST <br/>\r\n\t- minFilter: minifying filter: gl.NEAREST, gl.LINEAR, gl.LINEAR_MIPMAP_LINEAR, default gl.NEAREST <br/>\r\n\t- wrap: texture wrapping: gl.CLAMP_TO_EDGE, gl.REPEAT, gl.MIRROR, default gl.CLAMP_TO_EDGE (also accepts wrapT and wrapS for separate settings) <br/>\r\n\t- 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)<br/>\r\n\t- premultiply_alpha : multiply the color by the alpha value when uploading, default FALSE <br/>\r\n\t- no_flip : do not flip in Y, default TRUE <br/>\r\n\t- anisotropic : number of anisotropic fetches, default 0 <br/>\r\n\r\n\tcheck for more info about formats: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D\r\n\r\n* @class Texture\r\n* @param {number} width texture width (any supported but Power of Two allows to have mipmaps), 0 means no memory reserved till its filled\r\n* @param {number} height texture height (any supported but Power of Two allows to have mipmaps), 0 means no memory reserved till its filled\r\n* @param {Object} options Check the list in the description\r\n* @constructor\r\n*/\r\n\r\nglobal.Texture = GL.Texture = function Texture( width, height, options, gl ) {\r\n\toptions = options || {};\r\n\r\n\t//used to avoid problems with resources moving between different webgl context\r\n\tgl = gl || global.gl;\r\n\tthis.gl = gl;\r\n\tthis._context_id = gl.context_id; \r\n\r\n\t//round sizes\r\n\twidth = parseInt(width); \r\n\theight = parseInt(height);\r\n\r\n\tif(GL.debug)\r\n\t\tconsole.log(\"GL.Texture created: \",width,height);\r\n\r\n\t//create texture handler\r\n\tthis.handler = gl.createTexture();\r\n\r\n\t//set settings\r\n\tthis.width = width;\r\n\tthis.height = height;\r\n\tif(options.depth) //for texture_3d\r\n\t\tthis.depth = options.depth; \r\n\tthis.texture_type = options.texture_type || gl.TEXTURE_2D; //or gl.TEXTURE_CUBE_MAP\r\n\tthis.format = options.format || Texture.DEFAULT_FORMAT; //gl.RGBA (if gl.DEPTH_COMPONENT remember type: gl.UNSIGNED_SHORT)\r\n\tthis.internalFormat = options.internalFormat; //LUMINANCE, and weird formats with bits\r\n\tthis.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)\r\n\tthis.magFilter = options.magFilter || options.filter || Texture.DEFAULT_MAG_FILTER;\r\n\tthis.minFilter = options.minFilter || options.filter || Texture.DEFAULT_MIN_FILTER;\r\n\tthis.wrapS = options.wrap || options.wrapS || Texture.DEFAULT_WRAP_S; \r\n\tthis.wrapT = options.wrap || options.wrapT || Texture.DEFAULT_WRAP_T;\r\n\tthis.data = null; //where the data came from\r\n\r\n\t//precompute the max amount of texture units\r\n\tif(!Texture.MAX_TEXTURE_IMAGE_UNITS)\r\n\t\tTexture.MAX_TEXTURE_IMAGE_UNITS = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS );\r\n\r\n\tthis.has_mipmaps = false;\r\n\r\n\tif( this.format == gl.DEPTH_COMPONENT && gl.webgl_version == 1 && !gl.extensions[\"WEBGL_depth_texture\"] )\r\n\t\tthrow(\"Depth Texture not supported\");\r\n\tif( this.type == gl.FLOAT && !gl.extensions[\"OES_texture_float\"] && gl.webgl_version == 1 )\r\n\t\tthrow(\"Float Texture not supported\");\r\n\tif( this.type == gl.HALF_FLOAT_OES)\r\n\t{\r\n\t\tif( !gl.extensions[\"OES_texture_half_float\"] && gl.webgl_version == 1 )\r\n\t\t\tthrow(\"Half Float Texture extension not supported.\");\r\n\t\telse if( gl.webgl_version > 1 )\r\n\t\t{\r\n\t\t\tconsole.warn(\"using HALF_FLOAT_OES in WebGL2 is deprecated, suing HALF_FLOAT instead\");\r\n\t\t\tthis.type = this.format == gl.RGB ? gl.RGB16F : gl.RGBA16F;\r\n\t\t}\r\n\t}\r\n\tif( (!isPowerOfTwo(this.width) || !isPowerOfTwo(this.height)) && //non power of two\r\n\t\t( (this.minFilter != gl.NEAREST && this.minFilter != gl.LINEAR) || //uses mipmaps\r\n\t\t(this.wrapS != gl.CLAMP_TO_EDGE || this.wrapT != gl.CLAMP_TO_EDGE) ) ) //uses wrap\r\n\t{\r\n\t\tif(!options.ignore_pot)\r\n\t\t\tthrow(\"Cannot use texture-wrap or mipmaps in Non-Power-of-Two textures\");\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.minFilter = this.magFilter = gl.LINEAR;\r\n\t\t\tthis.wrapS = this.wrapT = gl.CLAMP_TO_EDGE;\r\n\t\t}\r\n\t}\r\n\r\n\t//empty textures are allowed to be created\r\n\tif(!width || !height)\r\n\t\treturn;\r\n\r\n\t//because sometimes the internal format is not so obvious\r\n\tif(!this.internalFormat)\r\n\t\tthis.computeInternalFormat();\r\n\r\n\t//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\r\n\tgl.activeTexture( gl.TEXTURE0 + Texture.MAX_TEXTURE_IMAGE_UNITS - 1);\r\n\t//I use an invalid gl enum to say this texture is a depth texture, ugly, I know...\r\n\tgl.bindTexture( this.texture_type, this.handler);\r\n\tgl.texParameteri( this.texture_type, gl.TEXTURE_MAG_FILTER, this.magFilter );\r\n\tgl.texParameteri( this.texture_type, gl.TEXTURE_MIN_FILTER, this.minFilter );\r\n\tgl.texParameteri( this.texture_type, gl.TEXTURE_WRAP_S, this.wrapS );\r\n\tgl.texParameteri( this.texture_type, gl.TEXTURE_WRAP_T, this.wrapT );\r\n\r\n\tif(options.anisotropic && gl.extensions[\"EXT_texture_filter_anisotropic\"])\r\n\t\tgl.texParameterf( GL.TEXTURE_2D, gl.extensions[\"EXT_texture_filter_anisotropic\"].TEXTURE_MAX_ANISOTROPY_EXT, options.anisotropic);\r\n\r\n\tvar type = this.type;\r\n\tvar pixel_data = options.pixel_data;\r\n\tif(pixel_data && !pixel_data.buffer)\r\n\t{\r\n\t\tif( this.texture_type == GL.TEXTURE_CUBE_MAP )\r\n\t\t{\r\n\t\t\tif(pixel_data[0].constructor === Number) //special case, specify just one face and copy it\r\n\t\t\t{\r\n\t\t\t\tpixel_data = toTypedArray( pixel_data );\r\n\t\t\t\tpixel_data = [pixel_data,pixel_data,pixel_data,pixel_data,pixel_data,pixel_data]; \r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tfor(var i = 0; i < pixel_data.length; ++i)\r\n\t\t\t\t\tpixel_data[i] = toTypedArray( pixel_data[i] );\r\n\t\t}\r\n\t\telse\r\n\t\t\tpixel_data = toTypedArray( pixel_data );\r\n\t\tthis.data = pixel_data;\r\n\t}\r\n\r\n\tfunction toTypedArray( data )\r\n\t{\r\n\t\tif(data.constructor !== Array)\r\n\t\t\treturn data;\r\n\t\tif( type == GL.FLOAT)\r\n\t\t\treturn new Float32Array( data );\r\n\t\tif( type == GL.HALF_FLOAT_OES)\r\n\t\t\treturn new Uint16Array( data );\r\n\t\treturn new Uint8Array( data );\r\n\t}\r\n\r\n\t//gl.TEXTURE_1D is not supported by WebGL...\r\n\r\n\t//here we create all **********************************\r\n\tif(this.texture_type == GL.TEXTURE_2D)\r\n\t{\r\n\t\t//create the texture\r\n\t\tgl.texImage2D( GL.TEXTURE_2D, 0, this.internalFormat, width, height, 0, this.format, this.type, pixel_data || null );\r\n\r\n\t\t//generate empty mipmaps (necessary?)\r\n\t\tif ( GL.isPowerOfTwo(width) && GL.isPowerOfTwo(height) && options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR)\r\n\t\t{\r\n\t\t\tgl.generateMipmap( this.texture_type );\r\n\t\t\tthis.has_mipmaps = true;\r\n\t\t}\r\n\t}\r\n\telse if(this.texture_type == GL.TEXTURE_CUBE_MAP)\r\n\t{\r\n\t\tfor(var i = 0; i < 6; ++i)\r\n\t\t\tgl.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 );\r\n\t}\r\n\telse if(this.texture_type == GL.TEXTURE_3D)\r\n\t{\r\n\t\tif(this.gl.webgl_version == 1)\r\n\t\t\tthrow(\"TEXTURE_3D not supported in WebGL 1. Enable WebGL 2 in the context by passing webgl2:true to the context\");\r\n\t\tif(!options.depth)\r\n\t\t\tthrow(\"3d texture depth must be set in the options.depth\");\r\n\t\tgl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false ); //standard does not allow this flags for 3D textures\r\n\t\tgl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false );\r\n\t\tgl.texImage3D( GL.TEXTURE_3D, 0, this.internalFormat, width, height, options.depth, 0, this.format, this.type, pixel_data || null );\r\n\t}\r\n\tgl.bindTexture(this.texture_type, null); //disable\r\n\tgl.activeTexture(gl.TEXTURE0);\r\n}\r\n\r\nTexture.DEFAULT_TYPE = GL.UNSIGNED_BYTE;\r\nTexture.DEFAULT_FORMAT = GL.RGBA;\r\nTexture.DEFAULT_MAG_FILTER = GL.LINEAR;\r\nTexture.DEFAULT_MIN_FILTER = GL.LINEAR;\r\nTexture.DEFAULT_WRAP_S = GL.CLAMP_TO_EDGE;\r\nTexture.DEFAULT_WRAP_T = GL.CLAMP_TO_EDGE;\r\nTexture.EXTENSION = \"png\"; //used when saving it to file\r\n\r\n//used for render to FBOs\r\nTexture.framebuffer = null;\r\nTexture.renderbuffer = null;\r\nTexture.loading_color = new Uint8Array([0,0,0,0]);\r\nTexture.use_renderbuffer_pool = true; //should improve performance\r\n\r\n//because usually you dont want to specify the internalFormat, this tries to guess it from its format\r\n//check https://webgl2fundamentals.org/webgl/lessons/webgl-data-textures.html for more info\r\nTexture.prototype.computeInternalFormat = function()\r\n{\r\n\tthis.internalFormat = this.format; //default\r\n\r\n\t//automatic selection of internal format for depth textures to avoid problems between webgl1 and 2\r\n\tif( this.format == GL.DEPTH_COMPONENT )\r\n\t{\r\n\t\tthis.minFilter = this.magFilter = GL.NEAREST;\r\n\r\n\t\tif( gl.webgl_version == 2 ) \r\n\t\t{\r\n\t\t\tif( this.type == GL.UNSIGNED_SHORT )\r\n\t\t\t\tthis.internalFormat = GL.DEPTH_COMPONENT16;\r\n\t\t\telse if( this.type == GL.UNSIGNED_INT )\r\n\t\t\t\tthis.internalFormat = GL.DEPTH_COMPONENT24;\r\n\t\t\telse if( this.type == GL.FLOAT )\r\n\t\t\t\tthis.internalFormat = GL.DEPTH_COMPONENT32F;\r\n\t\t\telse \r\n\t\t\t\tthrow(\"unsupported type for a depth texture\");\r\n\t\t}\r\n\t\telse if( gl.webgl_version == 1 )\r\n\t\t{\r\n\t\t\tif( this.type == GL.FLOAT )\r\n\t\t\t\tthrow(\"WebGL 1.0 does not support float depth textures\");\r\n\t\t\tthis.internalFormat = GL.DEPTH_COMPONENT;\r\n\t\t}\r\n\t}\r\n\telse if( this.format == gl.RGBA )\r\n\t{\r\n\t\tif( gl.webgl_version == 2 ) \r\n\t\t{\r\n\t\t\tif( this.type == GL.FLOAT )\r\n\t\t\t\tthis.internalFormat = GL.RGBA32F;\r\n\t\t\telse if( this.type == GL.HALF_FLOAT )\r\n\t\t\t\tthis.internalFormat = GL.RGBA16F;\r\n\t\t\telse if( this.type == GL.HALF_FLOAT_OES )\r\n\t\t\t{\r\n\t\t\t\tconsole.warn(\"webgl 2 does not use HALF_FLOAT_OES, converting to HALF_FLOAT\")\r\n\t\t\t\tthis.type = GL.HALF_FLOAT;\r\n\t\t\t\tthis.internalFormat = GL.RGBA16F;\r\n\t\t\t}\r\n\t\t\t/*\r\n\t\t\telse if( this.type == GL.UNSIGNED_SHORT )\r\n\t\t\t{\r\n\t\t\t\tthis.internalFormat = GL.RGBA16UI;\r\n\t\t\t\tthis.format = gl.RGBA_INTEGER;\r\n\t\t\t}\r\n\t\t\telse if( this.type == GL.UNSIGNED_INT )\r\n\t\t\t{\r\n\t\t\t\tthis.internalFormat = GL.RGBA32UI;\r\n\t\t\t\tthis.format = gl.RGBA_INTEGER;\r\n\t\t\t}\r\n\t\t\t*/\r\n\t\t}\r\n\t\telse if( gl.webgl_version == 1 )\r\n\t\t{\r\n\t\t\tif( this.type == GL.HALF_FLOAT )\r\n\t\t\t{\r\n\t\t\t\tconsole.warn(\"webgl 1 does not use HALF_FLOAT, converting to HALF_FLOAT_OES\")\r\n\t\t\t\tthis.type = GL.HALF_FLOAT_OES;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/**\r\n* Free the texture memory from the GPU, sets the texture handler to null\r\n* @method delete\r\n*/\r\nTexture.prototype.delete = function()\r\n{\r\n\tgl.deleteTexture( this.handler );\r\n\tthis.handler = null;\r\n}\r\n\r\nTexture.prototype.getProperties = function()\r\n{\r\n\treturn {\r\n\t\twidth: this.width,\r\n\t\theight: this.height,\r\n\t\ttype: this.type,\r\n\t\tformat: this.format,\r\n\t\ttexture_type: this.texture_type,\r\n\t\tmagFilter: this.magFilter,\r\n\t\tminFilter: this.minFilter,\r\n\t\twrapS: this.wrapS,\r\n\t\twrapT: this.wrapT\r\n\t};\r\n}\r\n\r\nTexture.prototype.hasSameProperties = function(t)\r\n{\r\n\tif(!t)\r\n\t\treturn false;\r\n\treturn t.width == this.width && \r\n\t\tt.height == this.height &&\r\n\t\tt.type == this.type &&\r\n\t\tt.format == this.format &&\r\n\t\tt.texture_type == this.texture_type;\r\n}\r\n\r\nTexture.prototype.hasSameSize = function(t)\r\n{\r\n\tif(!t)\r\n\t\treturn false;\r\n\treturn t.width == this.width && t.height == this.height;\r\n}\r\n//textures cannot be stored in JSON\r\nTexture.prototype.toJSON = function()\r\n{\r\n\treturn \"\";\r\n}\r\n\r\n\r\n/**\r\n* Returns if depth texture is supported by the GPU\r\n* @method isDepthSupported\r\n* @return {Boolean} true if supported\r\n*/\r\nTexture.isDepthSupported = function()\r\n{\r\n\treturn gl.extensions[\"WEBGL_depth_texture\"] != null;\r\n}\r\n\r\n/**\r\n* Binds the texture to one texture unit\r\n* @method bind\r\n* @param {number} unit texture unit\r\n* @return {number} returns the texture unit\r\n*/\r\nTexture.prototype.bind = function( unit ) {\r\n\tif(unit == undefined)\r\n\t\tunit = 0;\r\n\tvar gl = this.gl;\r\n\r\n\t//TODO: if the texture is not uploaded, must be upload now\r\n\r\n\t//bind\r\n\tgl.activeTexture(gl.TEXTURE0 + unit);\r\n\tgl.bindTexture( this.texture_type, this.handler );\r\n\treturn unit;\r\n}\r\n\r\n/**\r\n* Unbinds the texture \r\n* @method unbind\r\n* @param {number} unit texture unit\r\n* @return {number} returns the texture unit\r\n*/\r\nTexture.prototype.unbind = function(unit) {\r\n\tif(unit === undefined)\r\n\t\tunit = 0;\r\n\tvar gl = this.gl;\r\n\tgl.activeTexture(gl.TEXTURE0 + unit );\r\n\tgl.bindTexture(this.texture_type, null);\r\n}\r\n\r\n\r\nTexture.prototype.setParameter = function(param,value) {\r\n\tthis.bind(0);\r\n\tthis.gl.texParameteri( this.texture_type, param, value );\r\n\tswitch(param)\r\n\t{\r\n\t\tcase this.gl.TEXTURE_MAG_FILTER: this.magFilter = value; break;\r\n\t\tcase this.gl.TEXTURE_MIN_FILTER: this.minFilter = value; break;\r\n\t\tcase this.gl.TEXTURE_WRAP_S: this.wrapS = value; break;\r\n\t\tcase this.gl.TEXTURE_WRAP_T: this.wrapT = value; break;\r\n\t}\r\n}\r\n\r\n/**\r\n* Unbinds the texture \r\n* @method Texture.setUploadOptions\r\n* @param {Object} options a list of options to upload the texture\r\n* - premultiply_alpha : multiply the color by the alpha value, default FALSE\r\n* - no_flip : do not flip in Y, default TRUE\r\n*/\r\nTexture.setUploadOptions = function(options, gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\r\n\tif(options) //options that are not stored in the texture should be passed again to avoid reusing unknown state\r\n\t{\r\n\t\tgl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, !!(options.premultiply_alpha) );\r\n\t\tgl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !(options.no_flip) );\r\n\t}\r\n\telse\r\n\t{\r\n\t\tgl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false );\r\n\t\tgl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true );\r\n\t}\r\n\tgl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);\r\n}\r\n\r\n/**\r\n* Given an Image/Canvas/Video it uploads it to the GPU\r\n* @method uploadImage\r\n* @param {Image} img\r\n* @param {Object} options [optional] upload options (premultiply_alpha, no_flip)\r\n*/\r\nTexture.prototype.uploadImage = function( image, options )\r\n{\r\n\tthis.bind();\r\n\tvar gl = this.gl;\r\n\tif(!image)\r\n\t\tthrow(\"uploadImage parameter must be Image\");\r\n\r\n\tTexture.setUploadOptions(options, gl);\r\n\r\n\ttry {\r\n\t\tgl.texImage2D( gl.TEXTURE_2D, 0, this.format, this.format, this.type, image );\r\n\t\tthis.width = image.videoWidth || image.width;\r\n\t\tthis.height = image.videoHeight || image.height;\r\n\t\tthis.data = image;\r\n\t} catch (e) {\r\n\t\tif (location.protocol == 'file:') {\r\n\t\t\tthrow 'image not loaded for security reasons (serve this page over \"http://\" instead)';\r\n\t\t} else {\r\n\t\t\tthrow 'image not loaded for security reasons (image must originate from the same ' +\r\n\t\t\t'domain as this page or use Cross-Origin Resource Sharing)';\r\n\t\t}\r\n\t}\r\n\r\n\t//TODO: add expand transparent pixels option\r\n\r\n\t//generate mipmaps\r\n\tif (this.minFilter && this.minFilter != gl.NEAREST && this.minFilter != gl.LINEAR) {\r\n\t\tgl.generateMipmap(this.texture_type);\r\n\t\tthis.has_mipmaps = true;\r\n\t}\r\n\tgl.bindTexture(this.texture_type, null); //disable\r\n}\r\n\r\n/**\r\n* Uploads data to the GPU (data must have the appropiate size)\r\n* @method uploadData\r\n* @param {ArrayBuffer} data\r\n* @param {Object} options [optional] upload options (premultiply_alpha, no_flip, cubemap_face, mipmap_level)\r\n*/\r\nTexture.prototype.uploadData = function( data, options, skip_mipmaps )\r\n{\r\n\toptions = options || {};\r\n\tif(!data)\r\n\t\tthrow(\"no data passed\");\r\n\tvar gl = this.gl;\r\n\tthis.bind();\r\n\tTexture.setUploadOptions(options, gl);\r\n\tvar mipmap_level = options.mipmap_level || 0;\r\n\tvar width = this.width;\r\n\tvar height = this.height;\r\n\twidth = width >> mipmap_level; \r\n\theight = height >> mipmap_level;\r\n\tvar internal_format = this.internalFormat || this.format;\r\n\r\n\tif( this.type == GL.HALF_FLOAT_OES && data.constructor === Float32Array )\r\n\t\tconsole.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.\");\r\n\r\n\tif( this.texture_type == GL.TEXTURE_2D )\r\n\t{\r\n\t\tif(gl.webgl_version == 1)\r\n\t\t{\r\n\t\t\tif(data.buffer && data.buffer.constructor == ArrayBuffer)\r\n\t\t\t\tgl.texImage2D(this.texture_type, mipmap_level, internal_format, width, height, 0, this.format, this.type, data);\r\n\t\t\telse\r\n\t\t\t\tgl.texImage2D(this.texture_type, mipmap_level, internal_format, this.format, this.type, data);\r\n\t\t}\r\n\t\telse if(gl.webgl_version == 2) //webgl forces to use width and height\r\n\t\t{\r\n\t\t\tif(data.buffer && data.buffer.constructor == ArrayBuffer)\r\n\t\t\t\tgl.texImage2D(this.texture_type, mipmap_level, internal_format, width, height, 0, this.format, this.type, data);\r\n\t\t\telse\r\n\t\t\t\tgl.texImage2D(this.texture_type, mipmap_level, internal_format, width, height, 0, this.format, this.type, data);\r\n\t\t}\r\n\t}\r\n\telse if( this.texture_type == GL.TEXTURE_3D )\r\n\t{\r\n\t\tgl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false ); //standard does not allow this flags for 3D textures\r\n\t\tgl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false );\r\n\t\tgl.texImage3D( this.texture_type, mipmap_level, internal_format, width, height, this.depth >> mipmap_level, 0, this.format, this.type, data);\r\n\t}\r\n\telse if( this.texture_type == GL.TEXTURE_CUBE_MAP )\r\n\t\tgl.texImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + (options.cubemap_face || 0), mipmap_level, internal_format, width, height, 0, this.format, this.type, data);\r\n\telse\r\n\t\tthrow(\"cannot uploadData for this texture type\");\r\n\r\n\tthis.data = data; //should I clone it?\r\n\r\n\tif (!skip_mipmaps && this.minFilter && this.minFilter != gl.NEAREST && this.minFilter != gl.LINEAR) {\r\n\t\tgl.generateMipmap(this.texture_type);\r\n\t\tthis.has_mipmaps = true;\r\n\t}\r\n\tgl.bindTexture(this.texture_type, null); //disable\r\n}\r\n\r\n//When creating cubemaps this is helpful\r\n\r\n/*THIS WORKS old\r\nTexture.cubemap_camera_parameters = [\r\n\t{ type:\"posX\", dir: vec3.fromValues(-1,0,0), \tup: vec3.fromValues(0,1,0),\tright: vec3.fromValues(0,0,-1) },\r\n\t{ type:\"negX\", dir: vec3.fromValues(1,0,0),\t\tup: vec3.fromValues(0,1,0),\tright: vec3.fromValues(0,0,1) },\r\n\t{ type:\"posY\", dir: vec3.fromValues(0,-1,0), \tup: vec3.fromValues(0,0,-1), right: vec3.fromValues(1,0,0) },\r\n\t{ type:\"negY\", dir: vec3.fromValues(0,1,0),\t\tup: vec3.fromValues(0,0,1),\tright: vec3.fromValues(-1,0,0) },\r\n\t{ type:\"posZ\", dir: vec3.fromValues(0,0,-1), \tup: vec3.fromValues(0,1,0),\tright: vec3.fromValues(1,0,0) },\r\n\t{ type:\"negZ\", dir: vec3.fromValues(0,0,1),\t\tup: vec3.fromValues(0,1,0),\tright: vec3.fromValues(-1,0,0) }\r\n];\r\n*/\r\n\r\n//THIS works\r\nTexture.cubemap_camera_parameters = [\r\n\t{ type:\"posX\", dir: vec3.fromValues(1,0,0), \tup: vec3.fromValues(0,1,0),\tright: vec3.fromValues(0,0,-1) },\r\n\t{ type:\"negX\", dir: vec3.fromValues(-1,0,0),\tup: vec3.fromValues(0,1,0),\tright: vec3.fromValues(0,0,1) },\r\n\t{ type:\"posY\", dir: vec3.fromValues(0,1,0), \tup: vec3.fromValues(0,0,-1), right: vec3.fromValues(1,0,0) },\r\n\t{ type:\"negY\", dir: vec3.fromValues(0,-1,0),\tup: vec3.fromValues(0,0,1),\tright: vec3.fromValues(1,0,0) },\r\n\t{ type:\"posZ\", dir: vec3.fromValues(0,0,1), \tup: vec3.fromValues(0,1,0),\tright: vec3.fromValues(1,0,0) },\r\n\t{ type:\"negZ\", dir: vec3.fromValues(0,0,-1),\tup: vec3.fromValues(0,1,0),\tright: vec3.fromValues(-1,0,0) }\r\n];\r\n\r\n\r\n\r\n/**\r\n* Render to texture using FBO, just pass the callback to a rendering function and the content of the texture will be updated\r\n* 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\r\n* 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.\r\n*\r\n* 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)\r\n* @method drawTo\r\n* @param {Function} callback function that does all the rendering inside this texture\r\n*/\r\nTexture.prototype.drawTo = function(callback, params)\r\n{\r\n\tvar gl = this.gl;\r\n\r\n\t//if(this.format == gl.DEPTH_COMPONENT)\r\n\t//\tthrow(\"cannot use drawTo in depth textures, use Texture.drawToColorAndDepth\");\r\n\r\n\tvar v = gl.getViewport();\r\n\tvar now = GL.getTime();\r\n\r\n\tvar old_fbo = gl.getParameter( gl.FRAMEBUFFER_BINDING );\r\n\r\n\tvar framebuffer = gl._framebuffer = gl._framebuffer || gl.createFramebuffer();\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer );\r\n\r\n\t//this code allows to reuse old renderbuffers instead of creating and destroying them for every frame\r\n\tvar renderbuffer = null;\r\n\r\n\tif( Texture.use_renderbuffer_pool ) //create a renderbuffer pool\r\n\t{\r\n\t\tif(!gl._renderbuffers_pool)\r\n\t\t\tgl._renderbuffers_pool = {};\r\n\t\t//generate unique key for this renderbuffer\r\n\t\tvar key = this.width + \":\" + this.height;\r\n\r\n\t\t//reuse or create new one\r\n\t\tif( gl._renderbuffers_pool[ key ] ) //Reuse old\r\n\t\t{\r\n\t\t\trenderbuffer = gl._renderbuffers_pool[ key ];\r\n\t\t\trenderbuffer.time = now;\r\n\t\t\tgl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t//create temporary buffer\r\n\t\t\tgl._renderbuffers_pool[ key ] = renderbuffer = gl.createRenderbuffer();\r\n\t\t\trenderbuffer.time = now;\r\n\t\t\trenderbuffer.width = this.width;\r\n\t\t\trenderbuffer.height = this.height;\r\n\t\t\tgl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer );\r\n\r\n\t\t\t//destroy after one minute \r\n\t\t\tsetTimeout( inner_check_destroy.bind(renderbuffer), 1000*60 );\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\trenderbuffer = gl._renderbuffer = gl._renderbuffer || gl.createRenderbuffer();\r\n\t\trenderbuffer.width = this.width;\r\n\t\trenderbuffer.height = this.height;\r\n\t\tgl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer );\r\n\t}\r\n\r\n\r\n\t//bind render buffer for depth or color\r\n\tif( this.format === gl.DEPTH_COMPONENT )\r\n\t\tgl.renderbufferStorage( gl.RENDERBUFFER, gl.RGBA4, this.width, this.height);\r\n\telse\r\n\t\tgl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height);\r\n\r\n\r\n\t//clears memory from unused buffer\r\n\tfunction inner_check_destroy()\r\n\t{\r\n\t\tif( GL.getTime() - this.time >= 1000*60 )\r\n\t\t{\r\n\t\t\t//console.log(\"Buffer cleared\");\r\n\t\t\tgl.deleteRenderbuffer( gl._renderbuffers_pool[ key ] );\r\n\t\t\tdelete gl._renderbuffers_pool[ key ];\r\n\t\t}\r\n\t\telse\r\n\t\t\tsetTimeout( inner_check_destroy.bind(this), 1000*60 );\r\n\t}\r\n\r\n\r\n\t//create to store depth\r\n\t/*\r\n\tif (this.width != renderbuffer.width || this.height != renderbuffer.height ) {\r\n\t  renderbuffer.width = this.width;\r\n\t  renderbuffer.height = this.height;\r\n\t  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height);\r\n\t}\r\n\t*/\r\n\r\n\tgl.viewport(0, 0, this.width, this.height);\r\n\r\n\t//if(gl._current_texture_drawto)\r\n\t//\tthrow(\"Texture.drawTo: Cannot use drawTo from inside another drawTo\");\r\n\r\n\tgl._current_texture_drawto = this;\r\n\tgl._current_fbo_color = framebuffer;\r\n\tgl._current_fbo_depth = renderbuffer;\r\n\r\n\tif(this.texture_type == gl.TEXTURE_2D)\r\n\t{\r\n\t\tif( this.format !== gl.DEPTH_COMPONENT )\r\n\t\t{\r\n\t\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.handler, 0 );\r\n\t\t\tgl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tgl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, renderbuffer );\r\n\t\t\tgl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D,  this.handler, 0);\r\n\t\t}\r\n\t\tcallback(this, params);\r\n\t}\r\n\telse if(this.texture_type == gl.TEXTURE_CUBE_MAP)\r\n\t{\r\n\t\t//bind the fixed ones out of the loop to save calls\r\n\t\tif( this.format !== gl.DEPTH_COMPONENT )\r\n\t\t\tgl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer );\r\n\t\telse\r\n\t\t\tgl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, renderbuffer );\r\n\r\n\t\t//for every face of the cubemap\r\n\t\tfor(var i = 0; i < 6; i++)\r\n\t\t{\r\n\t\t\tif( this.format !== gl.DEPTH_COMPONENT )\r\n\t\t\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, this.handler, 0);\r\n\t\t\telse\r\n\t\t\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i,  this.handler, 0 );\r\n\t\t\tcallback(this,i, params);\r\n\t\t}\r\n\t}\r\n\r\n\tthis.data = null;\r\n\r\n\tgl._current_texture_drawto = null;\r\n\tgl._current_fbo_color = null;\r\n\tgl._current_fbo_depth = null;\r\n\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER, old_fbo );\r\n\tgl.bindRenderbuffer(gl.RENDERBUFFER, null);\r\n\tgl.viewport(v[0], v[1], v[2], v[3]);\r\n\r\n\treturn this;\r\n}\r\n\r\n/**\r\n* Static version of drawTo meant to be used with several buffers\r\n* @method drawToColorAndDepth\r\n* @param {Texture} color_texture\r\n* @param {Texture} depth_texture\r\n* @param {Function} callback\r\n*/\r\nTexture.drawTo = function( color_textures, callback, depth_texture )\r\n{\r\n\tvar w = -1,\r\n\t\th = -1,\r\n\t\ttype = null;\r\n\r\n\tif(!color_textures && !depth_texture)\r\n\t\tthrow(\"Textures missing in drawTo\");\r\n\r\n\tif(color_textures && color_textures.length)\r\n\t{\r\n\t\tfor(var i = 0; i < color_textures.length; i++)\r\n\t\t{\r\n\t\t\tvar t = color_textures[i];\r\n\t\t\tif(w == -1) \r\n\t\t\t\tw = t.width;\r\n\t\t\telse if(w != t.width)\r\n\t\t\t\tthrow(\"Cannot use Texture.drawTo if textures have different dimensions\");\r\n\t\t\tif(h == -1) \r\n\t\t\t\th = t.height;\r\n\t\t\telse if(h != t.height)\r\n\t\t\t\tthrow(\"Cannot use Texture.drawTo if textures have different dimensions\");\r\n\t\t\tif(type == null) //first one defines the type\r\n\t\t\t\ttype = t.type;\r\n\t\t\telse if (type != t.type)\r\n\t\t\t\tthrow(\"Cannot use Texture.drawTo if textures have different data type, all must have the same type\");\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tw = depth_texture.width;\r\n\t\th = depth_texture.height;\r\n\t}\r\n\r\n\tvar ext = gl.extensions[\"WEBGL_draw_buffers\"];\r\n\tif(!ext && color_textures && color_textures.length > 1)\r\n\t\tthrow(\"Rendering to several textures not supported\");\r\n\r\n\tvar v = gl.getViewport();\r\n\tgl._framebuffer =  gl._framebuffer || gl.createFramebuffer();\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER,  gl._framebuffer );\r\n\r\n\tgl.viewport( 0, 0, w, h );\r\n\r\n\tvar renderbuffer = null;\r\n\tif( depth_texture && depth_texture.format !== gl.DEPTH_COMPONENT || depth_texture.type != gl.UNSIGNED_INT )\r\n\t\tthrow(\"Depth texture must be of format: gl.DEPTH_COMPONENT and type: gl.UNSIGNED_INT\");\r\n\r\n\tif( depth_texture )\r\n\t{\r\n\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depth_texture.handler, 0);\r\n\t}\r\n\telse //create a temporary depth renderbuffer\r\n\t{\r\n\t\t//create renderbuffer for depth\r\n\t\trenderbuffer = gl._renderbuffer = gl._renderbuffer || gl.createRenderbuffer();\r\n\t\trenderbuffer.width = w;\r\n\t\trenderbuffer.height = h;\r\n\t\tgl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer );\r\n\t\tgl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h);\r\n\r\n\t\tgl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer );\r\n\t}\r\n\r\n\tif( color_textures )\r\n\t{\r\n\t\tvar order = []; //draw_buffers request the use of an array with the order of the attachments\r\n\t\tfor(var i = 0; i < color_textures.length; i++)\r\n\t\t{\r\n\t\t\tvar t = color_textures[i];\r\n\t\t\tgl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, t.handler, 0);\r\n\t\t\torder.push( gl.COLOR_ATTACHMENT0 + i );\r\n\t\t}\r\n\r\n\t\tif(color_textures.length > 1)\r\n\t\t\text.drawBuffersWEBGL( order );\r\n\t}\r\n\telse //create temporary color render buffer\r\n\t{\r\n\t\tvar color_renderbuffer = this._color_renderbuffer = this._color_renderbuffer || gl.createRenderbuffer();\r\n\t\tcolor_renderbuffer.width = w;\r\n\t\tcolor_renderbuffer.height = h;\r\n\r\n\t\tgl.bindRenderbuffer( gl.RENDERBUFFER, color_renderbuffer );\r\n\t\tgl.renderbufferStorage( gl.RENDERBUFFER, gl.RGBA4, w, h );\r\n\r\n\t\tgl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color_renderbuffer );\r\n\t}\r\n\r\n\tvar complete = gl.checkFramebufferStatus( gl.FRAMEBUFFER );\r\n\tif(complete !== gl.FRAMEBUFFER_COMPLETE)\r\n\t\tthrow(\"FBO not complete: \" + complete);\r\n\r\n\tcallback();\r\n\r\n\t//clear data\r\n\tif(color_textures.length)\r\n\t\tfor(var i = 0; i < color_textures.length; ++i)\r\n\t\t\tcolor_textures[i].data = null;\r\n\r\n\tgl.bindFramebuffer(gl.FRAMEBUFFER, null);\r\n\r\n\tgl.viewport(v[0], v[1], v[2], v[3]);\r\n}\r\n\r\n/**\r\n* Similar to drawTo but it also stores the depth in a depth texture\r\n* @method drawToColorAndDepth\r\n* @param {Texture} color_texture\r\n* @param {Texture} depth_texture\r\n* @param {Function} callback\r\n*/\r\nTexture.drawToColorAndDepth = function( color_texture, depth_texture, callback ) {\r\n\tvar gl = color_texture.gl; //static function\r\n\r\n\tif(depth_texture.width != color_texture.width || depth_texture.height != color_texture.height)\r\n\t\tthrow(\"Different size between color texture and depth texture\");\r\n\r\n\tvar v = gl.getViewport();\r\n\r\n\tgl._framebuffer =  gl._framebuffer || gl.createFramebuffer();\r\n\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER,  gl._framebuffer);\r\n\r\n\tgl.viewport(0, 0, color_texture.width, color_texture.height);\r\n\r\n\tgl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, color_texture.handler, 0);\r\n\tgl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT,  gl.TEXTURE_2D, depth_texture.handler, 0);\r\n\r\n\tcallback();\r\n\r\n\tcolor_texture.data = null;\r\n\tdepth_texture.data = null;\r\n\r\n\tgl.bindFramebuffer(gl.FRAMEBUFFER, null);\r\n\r\n\tgl.viewport(v[0], v[1], v[2], v[3]);\r\n}\r\n\r\n\r\n\r\n/**\r\n* Copy content of one texture into another\r\n* TODO: check using copyTexImage2D\r\n* @method copyTo\r\n* @param {GL.Texture} target_texture\r\n* @param {GL.Shader} [shader=null] optional shader to apply while copying\r\n* @param {Object} [uniforms=null] optional uniforms for the shader\r\n*/\r\nTexture.prototype.copyTo = function( target_texture, shader, uniforms ) {\r\n\tvar that = this;\r\n\tvar gl = this.gl;\r\n\tif(!target_texture)\r\n\t\tthrow(\"target_texture required\");\r\n\r\n\t//save state\r\n\tvar previous_fbo = gl.getParameter( gl.FRAMEBUFFER_BINDING );\r\n\tvar viewport = gl.getViewport(); \r\n\r\n\tif(!shader)\r\n\t\tshader = this.texture_type == gl.TEXTURE_2D ? GL.Shader.getScreenShader() : GL.Shader.getCubemapCopyShader();\r\n\r\n\t//render\r\n\tgl.disable( gl.BLEND );\r\n\tgl.disable( gl.DEPTH_TEST );\r\n\tif(shader && uniforms)\r\n\t\tshader.uniforms( uniforms );\r\n\r\n\t//reuse fbo\r\n\tvar fbo = gl.__copy_fbo;\r\n\tif(!fbo)\r\n\t\tfbo = gl.__copy_fbo = gl.createFramebuffer();\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER, fbo );\r\n\r\n\tgl.viewport(0,0,target_texture.width, target_texture.height);\r\n\tif(this.texture_type == gl.TEXTURE_2D)\r\n\t{\r\n\t\t//regular color texture\r\n\t\tif(this.format !== gl.DEPTH_COMPONENT && this.format !== gl.DEPTH_STENCIL )\r\n\t\t{\r\n\t\t\t/* doesnt work\r\n\t\t\tif( this.width == target_texture.width && this.height == target_texture.height && this.format == target_texture.format)\r\n\t\t\t{\r\n\t\t\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.handler, 0);\r\n\t\t\t\tgl.bindTexture( target_texture.texture_type, target_texture.handler );\r\n\t\t\t\tgl.copyTexImage2D( target_texture.texture_type, 0, this.format, 0, 0, target_texture.width, target_texture.height, 0);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t*/\r\n\t\t\t{\r\n\t\t\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target_texture.handler, 0);\r\n\t\t\t\tthis.toViewport( shader );\r\n\t\t\t}\r\n\t\t}\r\n\t\telse //copying a depth texture is harder\r\n\t\t{\r\n\t\t\tvar color_renderbuffer = gl._color_renderbuffer = gl._color_renderbuffer || gl.createRenderbuffer();\r\n\t\t\tvar w = color_renderbuffer.width = target_texture.width;\r\n\t\t\tvar h = color_renderbuffer.height = target_texture.height;\r\n\t\t\t\r\n\t\t\t//attach color render buffer\r\n\t\t\tgl.bindRenderbuffer( gl.RENDERBUFFER, color_renderbuffer );\r\n\t\t\tgl.renderbufferStorage( gl.RENDERBUFFER, gl.RGBA4, w, h );\r\n\t\t\tgl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color_renderbuffer );\r\n\r\n\t\t\t//attach depth texture\r\n\t\t\tvar attachment_point = target_texture.format == gl.DEPTH_STENCIL ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;\r\n\t\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, attachment_point, gl.TEXTURE_2D, target_texture.handler, 0);\r\n\r\n\t\t\tvar complete = gl.checkFramebufferStatus( gl.FRAMEBUFFER );\r\n\t\t\tif(complete !== gl.FRAMEBUFFER_COMPLETE)\r\n\t\t\t\tthrow(\"FBO not complete: \" + complete);\r\n\r\n\t\t\t//enable depth test?\r\n\t\t\tgl.enable( gl.DEPTH_TEST );\r\n\t\t\tgl.depthFunc( gl.ALWAYS );\r\n\t\t\tgl.colorMask( false,false,false,false );\r\n\t\t\t//call shader that overwrites depth values\r\n\t\t\tshader = GL.Shader.getCopyDepthShader();\r\n\t\t\tthis.toViewport( shader );\r\n\t\t\tgl.colorMask( true,true,true,true );\r\n\t\t\tgl.disable( gl.DEPTH_TEST );\r\n\t\t\tgl.depthFunc( gl.LEQUAL );\r\n\t\t\tgl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, null );\r\n\t\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, attachment_point, gl.TEXTURE_2D, null, 0);\r\n\t\t}\r\n\t}\r\n\telse if(this.texture_type == gl.TEXTURE_CUBE_MAP)\r\n\t{\r\n\t\tshader.uniforms({u_texture: 0});\r\n\t\tvar rot_matrix = GL.temp_mat3;\r\n\t\tfor(var i = 0; i < 6; i++)\r\n\t\t{\r\n\t\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, target_texture.handler, 0);\r\n\t\t\tvar face_info = GL.Texture.cubemap_camera_parameters[ i ];\r\n\t\t\tmat3.identity( rot_matrix );\r\n\t\t\trot_matrix.set( face_info.right, 0 );\r\n\t\t\trot_matrix.set( face_info.up, 3 );\r\n\t\t\trot_matrix.set( face_info.dir, 6 );\r\n\t\t\t//mat3.invert(rot_matrix,rot_matrix);\r\n\t\t\tthis.toViewport( shader,{ u_rotation: rot_matrix });\r\n\t\t}\r\n\t}\r\n\t\r\n\t//restore previous state\r\n\tgl.setViewport(viewport); //restore viewport\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER, previous_fbo ); //restore fbo\r\n\r\n\t//generate mipmaps when needed\r\n\tif (target_texture.minFilter && target_texture.minFilter != gl.NEAREST && target_texture.minFilter != gl.LINEAR) {\r\n\t\ttarget_texture.bind();\r\n\t\tgl.generateMipmap(target_texture.texture_type);\r\n\t\ttarget_texture.has_mipmaps = true;\r\n\t}\r\n\r\n\ttarget_texture.data = null;\r\n\tgl.bindTexture( target_texture.texture_type, null ); //disable\r\n\treturn this;\r\n}\r\n\r\n\r\n/**\r\n* Similar to CopyTo, but more specific, only for color texture_2D. It doesnt change the blend flag\r\n* @method blit\r\n* @param {GL.Texture} target_texture\r\n* @param {GL.Shader} [shader=null] optional shader to apply while copying\r\n* @param {Object} [uniforms=null] optional uniforms for the shader\r\n*/\r\nTexture.prototype.blit = (function(){ \r\n\tvar viewport = new Float32Array(4);\t\r\n\t\r\n\treturn function( target_texture, shader, uniforms ) {\r\n\t\tvar that = this;\r\n\t\tvar gl = this.gl;\r\n\r\n\t\tif ( this.texture_type != gl.TEXTURE_2D || this.format === gl.DEPTH_COMPONENT || this.format === gl.DEPTH_STENCIL )\r\n\t\t\tthrow(\"blit only support TEXTURE_2D of RGB or RGBA. use copyTo instead\");\r\n\r\n\t\t//save state\r\n\t\tvar previous_fbo = gl.getParameter( gl.FRAMEBUFFER_BINDING );\r\n\t\tviewport.set( gl.viewport_data ); \r\n\r\n\t\tshader = shader || GL.Shader.getScreenShader();\r\n\t\tif(shader && uniforms)\r\n\t\t\tshader.uniforms( uniforms );\r\n\r\n\t\t//reuse fbo\r\n\t\tvar fbo = gl.__copy_fbo;\r\n\t\tif(!fbo)\r\n\t\t\tfbo = gl.__copy_fbo = gl.createFramebuffer();\r\n\t\tgl.bindFramebuffer( gl.FRAMEBUFFER, fbo );\r\n\r\n\t\tgl.viewport(0,0,target_texture.width, target_texture.height);\r\n\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target_texture.handler, 0);\r\n\r\n\t\tthis.bind(0);\r\n\t\tshader.draw( GL.Mesh.getScreenQuad(), gl.TRIANGLES );\r\n\t\t\r\n\t\t//restore previous state\r\n\t\tgl.setViewport(viewport); //restore viewport\r\n\t\tgl.bindFramebuffer( gl.FRAMEBUFFER, previous_fbo ); //restore fbo\r\n\r\n\t\ttarget_texture.data = null;\r\n\t\tgl.bindTexture( target_texture.texture_type, null ); //disable\r\n\t\treturn this;\r\n\t}\r\n})();\r\n\r\n/**\r\n* Render texture in a quad to full viewport size\r\n* @method toViewport\r\n* @param {Shader} shader to apply, otherwise a default textured shader is applied [optional]\r\n* @param {Object} uniforms for the shader if needed [optional]\r\n*/\r\nTexture.prototype.toViewport = function(shader, uniforms)\r\n{\r\n\tshader = shader || Shader.getScreenShader();\r\n\tvar mesh = Mesh.getScreenQuad();\r\n\tthis.bind(0);\r\n\t//shader.uniforms({u_texture: 0}); //never changes\r\n\tif(uniforms)\r\n\t\tshader.uniforms(uniforms);\r\n\tshader.draw( mesh, gl.TRIANGLES );\r\n}\r\n\r\n/**\r\n* Fills the texture with a constant color (uses gl.clear)\r\n* @method fill\r\n* @param {vec4} color rgba\r\n* @param {boolean} skip_mipmaps if true the mipmaps wont be updated\r\n*/\r\nTexture.prototype.fill = function(color, skip_mipmaps )\r\n{\r\n\tvar old_color = gl.getParameter( gl.COLOR_CLEAR_VALUE );\r\n\tgl.clearColor( color[0], color[1], color[2], color[3] );\r\n\tthis.drawTo( function() {\r\n\t\tgl.clear( gl.COLOR_BUFFER_BIT );\t\r\n\t});\r\n\tgl.clearColor( old_color[0], old_color[1], old_color[2], old_color[3] );\r\n\r\n\tif (!skip_mipmaps && this.minFilter && this.minFilter != gl.NEAREST && this.minFilter != gl.LINEAR ) {\r\n\t\tthis.bind();\r\n\t\tgl.generateMipmap( this.texture_type );\r\n\t\tthis.has_mipmaps = true;\r\n\t}\r\n}\r\n\r\n/**\r\n* Render texture in a quad of specified area\r\n* @method renderQuad\r\n* @param {number} x\r\n* @param {number} y\r\n* @param {number} width\r\n* @param {number} height\r\n*/\r\nTexture.prototype.renderQuad = (function() {\r\n\t//static variables: less garbage\r\n\tvar identity = mat3.create();\r\n\tvar pos = vec2.create();\r\n\tvar size = vec2.create();\r\n\tvar white = vec4.fromValues(1,1,1,1);\r\n\r\n\treturn (function(x,y,w,h, shader, uniforms)\r\n\t{\r\n\t\tpos[0] = x;\tpos[1] = y;\r\n\t\tsize[0] = w; size[1] = h;\r\n\r\n\t\tshader = shader || Shader.getQuadShader(this.gl);\r\n\t\tvar mesh = Mesh.getScreenQuad(this.gl);\r\n\t\tthis.bind(0);\r\n\t\tshader.uniforms({u_texture: 0, u_position: pos, u_color: white, u_size: size, u_viewport: gl.viewport_data.subarray(2,4), u_transform: identity });\r\n\t\tif(uniforms)\r\n\t\t\tshader.uniforms(uniforms);\r\n\t\tshader.draw( mesh, gl.TRIANGLES );\r\n\t});\r\n})();\r\n\r\n\r\n/**\r\n* Applies a blur filter of 5x5 pixels to the texture (be careful using it, it is slow)\r\n* @method applyBlur\r\n* @param {Number} offsetx scalar that multiplies the offset when fetching pixels horizontally (default 1)\r\n* @param {Number} offsety scalar that multiplies the offset when fetching pixels vertically (default 1)\r\n* @param {Number} intensity scalar that multiplies the result (default 1)\r\n* @param {Texture} output_texture [optional] if not passed the output is the own texture\r\n* @param {Texture} temp_texture blur needs a temp texture, if not supplied it will use the temporary textures pool\r\n* @return {Texture} returns the temp_texture in case you want to reuse it\r\n*/\r\nTexture.prototype.applyBlur = function( offsetx, offsety, intensity, output_texture, temp_texture )\r\n{\r\n\tvar that = this;\r\n\tvar gl = this.gl;\r\n\tif(offsetx === undefined)\r\n\t\toffsetx = 1;\r\n\tif(offsety === undefined)\r\n\t\toffsety = 1;\r\n\tgl.disable( gl.DEPTH_TEST );\r\n\tgl.disable( gl.BLEND );\r\n\toutput_texture = output_texture || this;\r\n\tvar is_temp = !temp_texture;\r\n\r\n\t//if(this === output_texture && this.texture_type === gl.TEXTURE_CUBE_MAP )\r\n\t//\tthrow(\"cannot use applyBlur in a texture with itself when blurring a CUBE_MAP\");\r\n\tif(temp_texture === output_texture)\r\n\t\tthrow(\"cannot use applyBlur in a texture using as temporary itself\");\r\n\r\n\tif(output_texture && this.texture_type !== output_texture.texture_type )\r\n\t\tthrow(\"cannot use applyBlur with textures of different texture_type\");\r\n\r\n\t//if(this.width != output_texture.width || this.height != output_texture.height)\r\n\t//\tthrow(\"cannot use applyBlur with an output texture of different size, it doesnt work\");\r\n\r\n\t//save state\r\n\tvar current_fbo = gl.getParameter( gl.FRAMEBUFFER_BINDING );\r\n\tvar viewport = gl.getViewport(); \r\n\r\n\t//reuse fbo\r\n\tvar fbo = gl.__copy_fbo;\r\n\tif(!fbo)\r\n\t\tfbo = gl.__copy_fbo = gl.createFramebuffer();\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER, fbo );\r\n\tgl.viewport(0,0, this.width, this.height);\r\n\r\n\tif( this.texture_type === gl.TEXTURE_2D )\r\n\t{\r\n\t\tvar shader = GL.Shader.getBlurShader();\r\n\r\n\t\tif(!temp_texture)\r\n\t\t\ttemp_texture = GL.Texture.getTemporary( this.width, this.height, this );\r\n\r\n\t\t//horizontal blur\r\n\t\tgl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, temp_texture.handler, 0);\r\n\t\tthis.toViewport( shader, {u_texture: 0, u_intensity: intensity, u_offset: [0, offsety / this.height ] });\r\n\r\n\t\t//vertical blur\r\n\t\tgl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, output_texture.handler, 0);\r\n\t\tgl.viewport(0,0,output_texture.width, output_texture.height);\r\n\t\ttemp_texture.toViewport( shader, {u_intensity: intensity, u_offset: [offsetx / temp_texture.width, 0] });\r\n\r\n\t\tif(is_temp)\r\n\t\t\tGL.Texture.releaseTemporary( temp_texture );\r\n\t}\r\n\telse if( this.texture_type === gl.TEXTURE_CUBE_MAP )\r\n\t{\r\n\t\t//var weights = new Float32Array([ 0.16/0.98, 0.15/0.98, 0.12/0.98, 0.09/0.98, 0.05/0.98 ]);\r\n\t\t//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\r\n\t\tvar shader = GL.Shader.getCubemapBlurShader();\r\n\t\tshader.uniforms({u_texture: 0, u_intensity: intensity, u_offset: [ offsetx / this.width, offsety / this.height ] });\r\n\t\tthis.bind(0);\r\n\t\tvar mesh = Mesh.getScreenQuad();\r\n\t\tmesh.bindBuffers( shader );\r\n\t\tshader.bind();\r\n\r\n\t\tvar destination = null;\r\n\t\t\r\n\t\tif(!temp_texture && output_texture == this) //we need a temporary texture\r\n\t\t\tdestination = temp_texture = GL.Texture.getTemporary( output_texture.width, output_texture.height, output_texture );\r\n\t\telse\r\n\t\t\tdestination = output_texture; //blur directly to output texture\r\n\r\n\t\tvar rot_matrix = GL.temp_mat3;\r\n\t\tfor(var i = 0; i < 6; ++i)\r\n\t\t{\r\n\t\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, destination.handler, 0);\r\n\t\t\tvar face_info = GL.Texture.cubemap_camera_parameters[ i ];\r\n\t\t\tmat3.identity(rot_matrix);\r\n\t\t\trot_matrix.set( face_info.right, 0 );\r\n\t\t\trot_matrix.set( face_info.up, 3 );\r\n\t\t\trot_matrix.set( face_info.dir, 6 );\r\n\t\t\t//mat3.invert(rot_matrix,rot_matrix);\r\n\t\t\tshader._setUniform( \"u_rotation\", rot_matrix );\r\n\t\t\tgl.drawArrays( gl.TRIANGLES, 0, 6 );\r\n\t\t}\r\n\r\n\t\tmesh.unbindBuffers( shader );\r\n\r\n\t\tif(temp_texture) //copy back \r\n\t\t\ttemp_texture.copyTo( output_texture );\r\n\r\n\t\tif(temp_texture && is_temp) //release temp\r\n\t\t\tGL.Texture.releaseTemporary( temp_texture );\r\n\t}\r\n\r\n\t//restore previous state\r\n\tgl.setViewport(viewport); //restore viewport\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER, current_fbo ); //restore fbo\r\n\r\n\toutput_texture.data = null;\r\n\r\n\t//generate mipmaps when needed\r\n\tif (output_texture.minFilter && output_texture.minFilter != gl.NEAREST && output_texture.minFilter != gl.LINEAR) {\r\n\t\toutput_texture.bind();\r\n\t\tgl.generateMipmap(output_texture.texture_type);\r\n\t\toutput_texture.has_mipmaps = true;\r\n\t}\r\n\r\n\tgl.bindTexture( output_texture.texture_type, null ); //disable\r\n}\r\n\r\n\r\n/**\r\n* Loads and uploads a texture from a url\r\n* @method Texture.fromURL\r\n* @param {String} url\r\n* @param {Object} options\r\n* @param {Function} on_complete\r\n* @return {Texture} the texture\r\n*/\r\nTexture.fromURL = function( url, options, on_complete, gl ) {\r\n\tgl = gl || global.gl;\r\n\r\n\toptions = options || {};\r\n\toptions = Object.create(options); //creates a new options using the old one as prototype\r\n\r\n\tvar texture = options.texture || new GL.Texture(1, 1, options, gl);\r\n\r\n\tif(url.length < 64)\r\n\t\ttexture.url = url;\r\n\ttexture.bind();\r\n\tvar default_color = options.temp_color || Texture.loading_color;\r\n\t//Texture.setUploadOptions(options);\r\n\tgl.pixelStorei(gl.UNPACK_ALIGNMENT, 4);\r\n\tvar temp_color = options.type == gl.FLOAT ? new Float32Array(default_color) : new Uint8Array(default_color);\r\n\tgl.texImage2D( gl.TEXTURE_2D, 0, texture.format, texture.width, texture.height, 0, texture.format, texture.type, temp_color );\r\n\tgl.bindTexture( texture.texture_type, null ); //disable\r\n\ttexture.ready = false;\r\n\r\n\tvar ext = null;\r\n\tif( options.extension ) //to force format\r\n\t\text = options.extension;\r\n\r\n\tif(!ext && url.length < 512) //avoid base64 urls\r\n\t{\r\n\t\tvar base = url;\r\n\t\tvar pos = url.indexOf(\"?\");\r\n\t\tif(pos != -1)\r\n\t\t\tbase = url.substr(0,pos);\r\n\t\tpos = base.lastIndexOf(\".\");\r\n\t\tif(pos != -1)\r\n\t\t\text = base.substr(pos+1).toLowerCase();\r\n\t}\r\n\r\n\tif( ext == \"dds\")\r\n\t{\r\n\t\tvar ext = gl.getExtension(\"WEBKIT_WEBGL_compressed_texture_s3tc\") || gl.getExtension(\"WEBGL_compressed_texture_s3tc\");\r\n\t\tvar new_texture = new GL.Texture(0,0, options, gl);\r\n\t\tDDS.loadDDSTextureEx(gl, ext, url, new_texture.handler, true, function(t) {\r\n\t\t\ttexture.texture_type = t.texture_type;\r\n\t\t\ttexture.handler = t;\r\n\t\t\tdelete texture[\"ready\"]; //texture.ready = true;\r\n\t\t\tif(on_complete)\r\n\t\t\t\ton_complete(texture, url);\r\n\t\t});\r\n\t}\r\n\telse if( ext == \"tga\" )\r\n\t{\r\n\t\tHttpRequest( url, null, function(data) {\r\n\t\t\tvar img_data = GL.Texture.parseTGA(data);\r\n\t\t\tif(!img_data)\r\n\t\t\t\treturn;\r\n\t\t\toptions.texture = texture;\r\n\t\t\tif(img_data.format == \"RGB\")\r\n\t\t\t\ttexture.format = gl.RGB;\r\n\t\t\ttexture = GL.Texture.fromMemory( img_data.width, img_data.height, img_data.pixels, options );\r\n\t\t\tdelete texture[\"ready\"]; //texture.ready = true;\r\n\t\t\tif(on_complete)\r\n\t\t\t\ton_complete( texture, url );\r\n\t\t},null,{ binary: true });\r\n\t}\r\n\telse //png,jpg,webp,...\r\n\t{\r\n\t\tvar image = new Image();\r\n\t\timage.src = url;\r\n\t\tvar that = this;\r\n\t\timage.onload = function()\r\n\t\t{\r\n\t\t\toptions.texture = texture;\r\n\t\t\tGL.Texture.fromImage(this, options);\r\n\t\t\tdelete texture[\"ready\"]; //texture.ready = true;\r\n\t\t\tif(on_complete)\r\n\t\t\t\ton_complete(texture, url);\r\n\t\t}\r\n\t\timage.onerror = function()\r\n\t\t{\r\n\t\t\tif(on_complete)\r\n\t\t\t\ton_complete(null);\r\n\t\t}\r\n\t}\r\n\r\n\treturn texture;\r\n};\r\n\r\nTexture.parseTGA = function(data)\r\n{\r\n\tif(!data || data.constructor !== ArrayBuffer)\r\n\t\tthrow( \"TGA: data must be ArrayBuffer\");\r\n\tdata = new Uint8Array(data);\r\n\tvar TGAheader = new Uint8Array( [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0] );\r\n\tvar TGAcompare = data.subarray(0,12);\r\n\tfor(var i = 0; i < TGAcompare.length; i++)\r\n\t\tif(TGAheader[i] != TGAcompare[i])\r\n\t\t{\r\n\t\t\tconsole.error(\"TGA header is not valid\");\r\n\t\t\treturn null; //not a TGA\r\n\t\t}\r\n\r\n\tvar header = data.subarray(12,18);\r\n\tvar img = {};\r\n\timg.width = header[1] * 256 + header[0];\r\n\timg.height = header[3] * 256 + header[2];\r\n\timg.bpp = header[4];\r\n\timg.bytesPerPixel = img.bpp / 8;\r\n\timg.imageSize = img.width * img.height * img.bytesPerPixel;\r\n\timg.pixels = data.subarray(18,18+img.imageSize);\r\n\timg.pixels = new Uint8Array( img.pixels ); \t//clone\r\n\tif(\t(header[5] & (1<<4)) == 0) //hack, needs swap\r\n\t{\r\n\t\t//TGA comes in BGR format so we swap it, this is slooooow\r\n\t\tfor(var i = 0; i < img.imageSize; i+= img.bytesPerPixel)\r\n\t\t{\r\n\t\t\tvar temp = img.pixels[i];\r\n\t\t\timg.pixels[i] = img.pixels[i+2];\r\n\t\t\timg.pixels[i+2] = temp;\r\n\t\t}\r\n\t\theader[5] |= 1<<4; //mark as swaped\r\n\t\timg.format = img.bpp == 32 ? \"RGBA\" : \"RGB\";\r\n\t}\r\n\telse\r\n\t\timg.format = img.bpp == 32 ? \"RGBA\" : \"RGB\";\r\n\t//some extra bytes to avoid alignment problems\r\n\t//img.pixels = new Uint8Array( img.imageSize + 14);\r\n\t//img.pixels.set( data.subarray(18,18+img.imageSize), 0);\r\n\timg.flipY = true;\r\n\t//img.format = img.bpp == 32 ? \"BGRA\" : \"BGR\";\r\n\t//trace(\"TGA info: \" + img.width + \"x\" + img.height );\r\n\treturn img;\r\n}\r\n\r\n/**\r\n* Create a texture from an Image\r\n* @method Texture.fromImage\r\n* @param {Image} image\r\n* @param {Object} options\r\n* @return {Texture} the texture\r\n*/\r\nTexture.fromImage = function( image, options ) {\r\n\toptions = options || {};\r\n\r\n\tvar texture = options.texture || new GL.Texture( image.width, image.height, options);\r\n\ttexture.uploadImage( image, options );\r\n\r\n\ttexture.bind();\r\n\tgl.texParameteri(texture.texture_type, gl.TEXTURE_MAG_FILTER, texture.magFilter );\r\n\tgl.texParameteri(texture.texture_type, gl.TEXTURE_MIN_FILTER, texture.minFilter );\r\n\tgl.texParameteri(texture.texture_type, gl.TEXTURE_WRAP_S, texture.wrapS );\r\n\tgl.texParameteri(texture.texture_type, gl.TEXTURE_WRAP_T, texture.wrapT );\r\n\r\n\tif (GL.isPowerOfTwo(texture.width) && GL.isPowerOfTwo(texture.height) )\r\n\t{\r\n\t\tif( options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR)\r\n\t\t{\r\n\t\t\ttexture.bind();\r\n\t\t\tgl.generateMipmap(texture.texture_type);\r\n\t\t\ttexture.has_mipmaps = true;\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\t//no mipmaps supported\r\n\t\tgl.texParameteri(texture.texture_type, gl.TEXTURE_MIN_FILTER, GL.LINEAR );\r\n\t\tgl.texParameteri(texture.texture_type, gl.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE );\r\n\t\tgl.texParameteri(texture.texture_type, gl.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE );\r\n\t\ttexture.has_mipmaps = false;\r\n\t}\r\n\tgl.bindTexture(texture.texture_type, null); //disable\r\n\ttexture.data = image;\r\n\tif(options.keep_image)\r\n\t\ttexture.img = image;\r\n\treturn texture;\r\n};\r\n\r\n/**\r\n* Create a texture from a Video\r\n* @method Texture.fromVideo\r\n* @param {Video} video\r\n* @param {Object} options\r\n* @return {Texture} the texture\r\n*/\r\nTexture.fromVideo = function(video, options) {\r\n\toptions = options || {};\r\n\r\n\tvar texture = options.texture || new GL.Texture(video.videoWidth, video.videoHeight, options);\r\n\ttexture.bind();\r\n\ttexture.uploadImage( video, options );\r\n\tif (options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR) {\r\n\t\ttexture.bind();\r\n\t\tgl.generateMipmap(texture.texture_type);\r\n\t\ttexture.has_mipmaps = true;\r\n\t\ttexture.data = video;\r\n\t}\r\n\tgl.bindTexture(texture.texture_type, null); //disable\r\n\treturn texture;\r\n};\r\n\r\n/**\r\n* Create a clone of a texture\r\n* @method Texture.fromTexture\r\n* @param {Texture} old_texture\r\n* @param {Object} options\r\n* @return {Texture} the texture\r\n*/\r\nTexture.fromTexture = function( old_texture, options) {\r\n\toptions = options || {};\r\n\tvar texture = new GL.Texture( old_texture.width, old_texture.height, options );\r\n\told_texture.copyTo( texture );\r\n\treturn texture;\r\n};\r\n\r\nTexture.prototype.clone = function( options )\r\n{\r\n\tvar old_options = this.getProperties();\r\n\tif(options)\r\n\t\tfor(var i in options)\r\n\t\t\told_options[i] = options[i];\r\n\treturn Texture.fromTexture( this, old_options);\r\n}\r\n\r\n/**\r\n* Create a texture from an ArrayBuffer containing the pixels\r\n* @method Texture.fromTexture\r\n* @param {number} width\r\n* @param {number} height\r\n* @param {ArrayBuffer} pixels\r\n* @param {Object} options\r\n* @return {Texture} the texture\r\n*/\r\nTexture.fromMemory = function( width, height, pixels, options) //format in options as format\r\n{\r\n\toptions = options || {};\r\n\r\n\tvar texture = options.texture || new GL.Texture(width, height, options);\r\n\tTexture.setUploadOptions(options);\r\n\ttexture.bind();\r\n\r\n\tif(pixels.constructor === Array)\r\n\t{\r\n\t\tif(options.type == gl.FLOAT)\r\n\t\t\tpixels = new Float32Array( pixels );\r\n\t\telse if(options.type == GL.HALF_FLOAT || options.type == GL.HALF_FLOAT_OES) \r\n\t\t\tpixels = new Uint16Array( pixels ); //gl.UNSIGNED_SHORT_4_4_4_4 is only for texture that are SHORT per pixel, not per channel!\r\n\t\telse \r\n\t\t\tpixels = new Uint8Array( pixels );\r\n\t}\r\n\r\n\tgl.texImage2D( gl.TEXTURE_2D, 0, texture.format, width, height, 0, texture.format, texture.type, pixels );\r\n\ttexture.width = width;\r\n\ttexture.height = height;\r\n\ttexture.data = pixels;\r\n\tif (options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR) {\r\n\t\tgl.generateMipmap(gl.TEXTURE_2D);\r\n\t\ttexture.has_mipmaps = true;\r\n\t}\r\n\tgl.bindTexture(texture.texture_type, null); //disable\r\n\treturn texture;\r\n};\r\n\r\n/**\r\n* Create a texture from an ArrayBuffer containing the pixels\r\n* @method Texture.fromDDSInMemory\r\n* @param {ArrayBuffer} DDS data\r\n* @param {Object} options\r\n* @return {Texture} the texture\r\n*/\r\nTexture.fromDDSInMemory = function(data, options) //format in options as format\r\n{\r\n\toptions = options || {};\r\n\r\n\tvar texture = options.texture || new GL.Texture(0, 0, options);\r\n\tGL.Texture.setUploadOptions(options);\r\n\ttexture.bind();\r\n\r\n\tvar ext = gl.getExtension(\"WEBKIT_WEBGL_compressed_texture_s3tc\") || gl.getExtension(\"WEBGL_compressed_texture_s3tc\");\r\n\tDDS.loadDDSTextureFromMemoryEx(gl, ext, data, texture, true );\r\n\r\n\tgl.bindTexture(texture.texture_type, null); //disable\r\n\treturn texture;\r\n};\r\n\r\n/**\r\n* Create a generative texture from a shader ( must GL.Shader.getScreenShader as reference for the shader )\r\n* @method Texture.fromShader\r\n* @param {number} width\r\n* @param {number} height\r\n* @param {Shader} shader\r\n* @param {Object} options\r\n* @return {Texture} the texture\r\n*/\r\nTexture.fromShader = function(width, height, shader, options) {\r\n\toptions = options || {};\r\n\t\r\n\tvar texture = new GL.Texture( width, height, options );\r\n\t//copy content\r\n\ttexture.drawTo(function() {\r\n\t\tgl.disable( gl.BLEND ); \r\n\t\tgl.disable( gl.DEPTH_TEST );\r\n\t\tgl.disable( gl.CULL_FACE );\r\n\t\tvar mesh = Mesh.getScreenQuad();\r\n\t\tshader.draw( mesh );\r\n\t});\r\n\r\n\treturn texture;\r\n};\r\n\r\n/**\r\n* Create a cubemap texture from a set of 6 images\r\n* @method Texture.cubemapFromImages\r\n* @param {Array} images\r\n* @param {Object} options\r\n* @return {Texture} the texture\r\n*/\r\nTexture.cubemapFromImages = function(images, options) {\r\n\toptions = options || {};\r\n\tif(images.length != 6)\r\n\t\tthrow \"missing images to create cubemap\";\r\n\r\n\tvar width = images[0].width;\r\n\tvar height = images[0].height;\r\n\toptions.texture_type = gl.TEXTURE_CUBE_MAP;\r\n\r\n\tvar texture = null;\r\n\t\r\n\tif(options.texture)\r\n\t{\r\n\t\ttexture = options.texture;\r\n\t\ttexture.width = width;\r\n\t\ttexture.height = height;\r\n\t}\r\n\telse\r\n\t\ttexture = new GL.Texture( width, height, options );\r\n\r\n\tTexture.setUploadOptions(options);\r\n\ttexture.bind();\r\n\r\n\ttry {\r\n\r\n\t\tfor(var i = 0; i < 6; i++)\r\n\t\t\tgl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, texture.format, texture.format, texture.type, images[i]);\r\n\t\ttexture.data = images;\r\n\t} catch (e) {\r\n\t\tif (location.protocol == 'file:') {\r\n\t\t  throw 'image not loaded for security reasons (serve this page over \"http://\" instead)';\r\n\t\t} else {\r\n\t\t  throw 'image not loaded for security reasons (image must originate from the same ' +\r\n\t\t\t'domain as this page or use Cross-Origin Resource Sharing)';\r\n\t\t}\r\n\t}\r\n\tif (options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR) {\r\n\t\tgl.generateMipmap(gl.TEXTURE_CUBE_MAP);\r\n\t\ttexture.has_mipmaps = true;\r\n\t}\r\n\r\n\ttexture.unbind();\r\n\treturn texture;\r\n};\r\n\r\n/**\r\n* Create a cubemap texture from a single image that contains all six images \r\n* 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)\r\n* otherwise it assumes the 6 images are arranged vertically, in the order of OpenGL: +X, -X, +Y, -Y, +Z, -Z\r\n* @method Texture.cubemapFromImage\r\n* @param {Image} image\r\n* @param {Object} options\r\n* @return {Texture} the texture\r\n*/\r\nTexture.cubemapFromImage = function( image, options ) {\r\n\toptions = options || {};\r\n\r\n\tif(image.width != (image.height / 6) && image.height % 6 != 0 && !options.faces && !options.is_polar )\r\n\t{\r\n\t\tconsole.error( \"Cubemap image not valid, only 1x6 (vertical) or 6x3 (cross) formats. Check size:\", image.width, image.height );\r\n\t\treturn null;\r\n\t}\r\n\r\n\tvar width = image.width;\r\n\tvar height = image.height;\r\n\r\n\tif(options.is_polar)\r\n\t{\r\n\t\tvar size = options.size || GL.nearestPowerOfTwo( image.height );\r\n\t\tvar temp_tex = GL.Texture.fromImage( image, { ignore_pot:true, wrap: gl.REPEAT, filter: gl.LINEAR } );\r\n\t\tvar cubemap = new GL.Texture( size, size, { texture_type: gl.TEXTURE_CUBE_MAP, format: gl.RGBA });\r\n\t\tif(options.texture)\r\n\t\t{\r\n\t\t\tvar old_tex = options.texture;\r\n\t\t\tfor(var i in cubemap)\r\n\t\t\t\told_tex[i] = cubemap[i];\r\n\t\t\tcubemap = old_tex;\r\n\t\t}\r\n\t\tvar rot_matrix = mat3.create();\r\n\t\tvar uniforms = { u_texture:0, u_rotation: rot_matrix };\r\n\t\tgl.disable( gl.DEPTH_TEST );\r\n\t\tgl.disable( gl.BLEND );\r\n\t\tvar shader = GL.Shader.getPolarToCubemapShader();\r\n\t\tcubemap.drawTo(function(t,i){\r\n\t\t\tvar face_info = GL.Texture.cubemap_camera_parameters[ i ];\r\n\t\t\tmat3.identity( rot_matrix );\r\n\t\t\trot_matrix.set( face_info.right, 0 );\r\n\t\t\trot_matrix.set( face_info.up, 3 );\r\n\t\t\trot_matrix.set( face_info.dir, 6 );\r\n\t\t\ttemp_tex.toViewport( shader, uniforms );\r\n\t\t});\r\n\t\tif(options.keep_image)\r\n\t\t\tcubemap.img = image;\r\n\t\treturn cubemap;\r\n\t}\r\n\telse if(options.is_cross !== undefined)\r\n\t{\r\n\t\toptions.faces = Texture.generateCubemapCrossFacesInfo(image.width, options.is_cross);\r\n\t\twidth = height = image.width / 4;\r\n\t}\r\n\telse if(options.faces)\r\n\t{\r\n\t\twidth = options.width || options.faces[0].width;\r\n\t\theight = options.height || options.faces[0].height;\r\n\t}\r\n\telse\r\n\t\theight /= 6;\r\n\r\n\tif(width != height)\r\n\t{\r\n\t\tconsole.log(\"Texture not valid, width and height for every face must be square\");\r\n\t\treturn null;\r\n\t}\r\n\r\n\tvar size = width;\r\n\toptions.no_flip = true;\r\n\r\n\tvar images = [];\r\n\tfor(var i = 0; i < 6; i++)\r\n\t{\r\n\t\tvar canvas = createCanvas( size, size );\r\n\t\tvar ctx = canvas.getContext(\"2d\");\r\n\t\tif(options.faces)\r\n\t\t\tctx.drawImage(image, options.faces[i].x, options.faces[i].y, options.faces[i].width || size, options.faces[i].height || size, 0,0, size, size );\r\n\t\telse\r\n\t\t\tctx.drawImage(image, 0, height*i, width, height, 0,0, size, size );\r\n\t\timages.push(canvas);\r\n\t\t//document.body.appendChild(canvas); //debug\r\n\t}\r\n\r\n\tvar texture = Texture.cubemapFromImages(images, options);\r\n\tif(options.keep_image)\r\n\t\ttexture.img = image;\r\n\treturn texture;\r\n};\r\n\r\n/**\r\n* 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\r\n* @method Texture.generateCubemapCrossFaces\r\n* @param {number} width of the CROSS image (not the side image)\r\n* @param {number} column the column where the top and the bottom is located\r\n* @return {Object} object to pass to Texture.cubemapFromImage in options.faces\r\n*/\r\nTexture.generateCubemapCrossFacesInfo = function(width, column)\r\n{\r\n\tif(column === undefined)\r\n\t\tcolumn = 1;\r\n\tvar s = width / 4;\r\n\r\n\treturn [\r\n\t\t{ x: 2*s, y: s, width: s, height: s }, //+x\r\n\t\t{ x: 0, y: s, width: s, height: s }, //-x\r\n\t\t{ x: column*s, y: 0, width: s, height: s }, //+y\r\n\t\t{ x: column*s, y: 2*s, width: s, height: s }, //-y\r\n\t\t{ x: s, y: s, width: s, height: s }, //+z\r\n\t\t{ x: 3*s, y: s, width: s, height: s } //-z\r\n\t];\r\n}\r\n\r\n/**\r\n* Create a cubemap texture from a single image url that contains the six images\r\n* 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)\r\n* otherwise it assumes the 6 images are arranged vertically.\r\n* @method Texture.cubemapFromURL\r\n* @param {Image} image\r\n* @param {Object} options\r\n* @param {Function} on_complete callback\r\n* @return {Texture} the texture\r\n*/\r\nTexture.cubemapFromURL = function( url, options, on_complete ) {\r\n\toptions = options || {};\r\n\toptions = Object.create(options); //creates a new options using the old one as prototype\r\n\toptions.texture_type = gl.TEXTURE_CUBE_MAP;\r\n\tvar texture = options.texture || new GL.Texture(1, 1, options);\r\n\r\n\ttexture.bind();\r\n\tTexture.setUploadOptions(options);\r\n\tvar default_color = options.temp_color || [0,0,0,255];\r\n\tvar temp_color = options.type == gl.FLOAT ? new Float32Array(default_color) : new Uint8Array(default_color);\r\n\r\n\tfor(var i = 0; i < 6; i++)\r\n\t\tgl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, texture.format, 1, 1, 0, texture.format, texture.type, temp_color);\r\n\tgl.bindTexture(texture.texture_type, null); //disable\r\n\ttexture.ready = false;\r\n\r\n\tvar image = new Image();\r\n\timage.src = url;\r\n\tvar that = this;\r\n\timage.onload = function()\r\n\t{\r\n\t\toptions.texture = texture;\r\n\t\ttexture = GL.Texture.cubemapFromImage(this, options);\r\n\t\tif(texture)\r\n\t\t\tdelete texture[\"ready\"]; //texture.ready = true;\r\n\t\tif(on_complete)\r\n\t\t\ton_complete(texture);\r\n\t}\r\n\r\n\treturn texture;\t\r\n};\r\n\r\n/**\r\n* returns an ArrayBuffer with the pixels in the texture, they are fliped in Y\r\n* Warn: If cubemap it only returns the pixels of the first face! use getCubemapPixels instead\r\n* @method getPixels\r\n* @param {number} cubemap_face [optional] the index of the cubemap face to read (ignore if texture_2D)\r\n* @param {number} mipmap level [optional, default is 0]\r\n* @return {ArrayBuffer} the data ( Uint8Array, Uint16Array or Float32Array )\r\n*/\r\nTexture.prototype.getPixels = function( cubemap_face, mipmap_level )\r\n{\r\n\tmipmap_level = mipmap_level || 0;\r\n\tvar gl = this.gl;\r\n\tvar v = gl.getViewport();\r\n\tvar old_fbo = gl.getParameter( gl.FRAMEBUFFER_BINDING );\r\n\r\n\tif(this.format == gl.DEPTH_COMPONENT)\r\n\t\tthrow(\"cannot use getPixels in depth textures\");\r\n\r\n\tgl.disable( gl.DEPTH_TEST );\r\n\r\n\t//reuse fbo\r\n\tvar fbo = gl.__copy_fbo;\r\n\tif(!fbo)\r\n\t\tfbo = gl.__copy_fbo = gl.createFramebuffer();\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER, fbo );\r\n\r\n\tvar buffer = null;\r\n\r\n\tvar width = this.width >> mipmap_level;\r\n\tvar height = this.height >> mipmap_level;\r\n\tgl.viewport(0, 0, width, height);\r\n\r\n\tif(this.texture_type == gl.TEXTURE_2D)\r\n\t\tgl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.handler, mipmap_level);\r\n\telse if(this.texture_type == gl.TEXTURE_CUBE_MAP)\r\n\t\tgl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + (cubemap_face || 0), this.handler, mipmap_level);\r\n\r\n\tvar channels = this.format == gl.RGB ? 3 : 4;\r\n\tchannels = 4; //WEBGL DOES NOT SUPPORT READING 3 CHANNELS ONLY, YET...\r\n\tvar type = this.type;\r\n\t//type = gl.UNSIGNED_BYTE; //WEBGL DOES NOT SUPPORT READING FLOAT seems, YET... 23/5/18 now it seems it does now\r\n\r\n\tif(type == gl.UNSIGNED_BYTE)\r\n\t\tbuffer = new Uint8Array( width * height * channels );\r\n\telse if(type == GL.HALF_FLOAT || type == GL.HALF_FLOAT_OES) //previously half float couldnot be read\r\n\t\tbuffer = new Uint16Array( width * height * channels ); //gl.UNSIGNED_SHORT_4_4_4_4 is only for texture that are SHORT per pixel, not per channel!\r\n\telse \r\n\t\tbuffer = new Float32Array( width * height * channels );\r\n\r\n\tgl.readPixels( 0,0, width, height, channels == 3 ? gl.RGB : gl.RGBA, type, buffer ); //NOT SUPPORTED FLOAT or RGB BY WEBGL YET\r\n\r\n\t//restore\r\n\tgl.bindFramebuffer(gl.FRAMEBUFFER, old_fbo );\r\n\tgl.viewport(v[0], v[1], v[2], v[3]);\r\n\treturn buffer;\r\n}\r\n\r\n/**\r\n* uploads some pixels to the texture (see uploadData method for more options)\r\n* @method setPixels\r\n* @param {ArrayBuffer} data gl.UNSIGNED_BYTE or gl.FLOAT data\r\n* @param {Boolean} no_flip do not flip in Y \r\n* @param {Boolean} skip_mipmaps do not update mipmaps when possible\r\n* @param {Number} cubemap_face if the texture is a cubemap, which face\r\n*/\r\nTexture.prototype.setPixels = function( data, no_flip, skip_mipmaps, cubemap_face )\r\n{\r\n\tvar options = { no_flip: no_flip };\r\n\tif(cubemap_face)\r\n\t\toptions.cubemap_face = cubemap_face;\r\n\tthis.uploadData( data, options, skip_mipmaps );\r\n}\r\n\r\n/**\r\n* returns an array with six arrays containing the pixels of every cubemap face\r\n* @method getCubemapPixels\r\n* @return {Array} the array that has 6 typed arrays containing the pixels \r\n*/\r\nTexture.prototype.getCubemapPixels = function()\r\n{\r\n\tif(this.texture_type !== gl.TEXTURE_CUBE_MAP)\r\n\t\tthrow(\"this texture is not a cubemap\");\r\n\treturn [ this.getPixels(0),\tthis.getPixels(1), this.getPixels(2), this.getPixels(3), this.getPixels(4), this.getPixels(5) ];\r\n}\r\n\r\n/**\r\n* fills a cubemap given an array with typed arrays containing the pixels of 6 faces\r\n* @method setCubemapPixels\r\n* @param {Array} data array that has 6 typed arrays containing the pixels \r\n* @param {bool} noflip if pixels should not be flipped according to Y\r\n*/\r\nTexture.prototype.setCubemapPixels = function( data_array, no_flip )\r\n{\r\n\tif(this.texture_type !== gl.TEXTURE_CUBE_MAP)\r\n\t\tthrow(\"this texture is not a cubemap, it should be created with { texture_type: gl.TEXTURE_CUBE_MAP }\");\r\n\tfor(var i = 0; i < 6; ++i)\r\n\t\tthis.setPixels( data_array[i], no_flip, i != 5, i );\r\n}\r\n\r\n/**\r\n* Copy texture content to a canvas\r\n* @method toCanvas\r\n* @param {Canvas} canvas must have the same size, if different the canvas will be resized\r\n* @param {boolean} flip_y optional, flip vertically\r\n* @param {Number} max_size optional, if it is supplied the canvas wont be bigger of max_size (the image will be scaled down)\r\n*/\r\nTexture.prototype.toCanvas = function( canvas, flip_y, max_size )\r\n{\r\n\tmax_size = max_size || 8192;\r\n\tvar gl = this.gl;\r\n\r\n\tvar w = Math.min( this.width, max_size );\r\n\tvar h = Math.min( this.height, max_size );\r\n\r\n\t//cross\r\n\tif(this.texture_type == gl.TEXTURE_CUBE_MAP)\r\n\t{\r\n\t\tw = w * 4;\r\n\t\th = h * 3;\r\n\t}\r\n\r\n\tcanvas = canvas || createCanvas( w, h );\r\n\tif(canvas.width != w) \r\n\t\tcanvas.width = w;\r\n\tif(canvas.height != h)\r\n\t\tcanvas.height = h;\r\n\r\n\tvar buffer = null;\r\n\tif(this.texture_type == gl.TEXTURE_2D )\r\n\t{\r\n\t\tif(this.width != w || this.height != h || this.type != gl.UNSIGNED_BYTE) //resize image to fit the canvas\r\n\t\t{\r\n\t\t\t//create a temporary texture\r\n\t\t\tvar temp = new GL.Texture(w,h,{ format: gl.RGBA, filter: gl.NEAREST });\r\n\t\t\tthis.copyTo( temp );\t\r\n\t\t\tbuffer = temp.getPixels();\r\n\t\t}\r\n\t\telse\r\n\t\t\tbuffer = this.getPixels();\r\n\r\n\t\tvar ctx = canvas.getContext(\"2d\");\r\n\t\tvar pixels = ctx.getImageData(0,0,w,h);\r\n\t\tpixels.data.set( buffer );\r\n\t\tctx.putImageData(pixels,0,0);\r\n\r\n\t\tif(flip_y)\r\n\t\t{\r\n\t\t\tvar temp = createCanvas(w,h);\r\n\t\t\tvar temp_ctx = temp.getContext(\"2d\");\r\n\t\t\ttemp_ctx.translate(0,temp.height);\r\n\t\t\ttemp_ctx.scale(1,-1);\r\n\t\t\ttemp_ctx.drawImage( canvas, 0, 0, temp.width, temp.height );\r\n\t\t\tctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);\r\n\t\t\tctx.drawImage( temp, 0, 0 );\r\n\t\t}\r\n\t}\r\n\telse if(this.texture_type == gl.TEXTURE_CUBE_MAP )\r\n\t{\r\n\t\tvar temp_canvas = createCanvas( this.width, this.height );\r\n\t\tvar temp_ctx = temp_canvas.getContext(\"2d\");\r\n\t\tvar info = GL.Texture.generateCubemapCrossFacesInfo( canvas.width, 1 );\r\n\t\tvar ctx = canvas.getContext(\"2d\");\r\n\t\tctx.fillStyle = \"black\";\r\n\t\tctx.fillRect(0,0,canvas.width, canvas.height );\r\n\r\n\t\tvar cubemap = this;\r\n\t\tif(this.type != gl.UNSIGNED_BYTE) //convert pixels to uint8 as it is the only supported format by the canvas\r\n\t\t{\r\n\t\t\t//create a temporary texture\r\n\t\t\tcubemap = new GL.Texture( this.width, this.height, { format: gl.RGBA, texture_type: gl.TEXTURE_CUBE_MAP, filter: gl.NEAREST, type: gl.UNSIGNED_BYTE });\r\n\t\t\tthis.copyTo( cubemap );\r\n\t\t}\r\n\r\n\t\tfor(var i = 0; i < 6; i++)\r\n\t\t{\r\n\t\t\tvar pixels = temp_ctx.getImageData(0,0, temp_canvas.width, temp_canvas.height );\r\n\t\t\tbuffer = cubemap.getPixels(i);\r\n\t\t\tpixels.data.set( buffer );\r\n\t\t\ttemp_ctx.putImageData(pixels,0,0);\r\n\t\t\tctx.drawImage( temp_canvas, info[i].x, info[i].y, temp_canvas.width, temp_canvas.height );\r\n\t\t}\r\n\t}\r\n\r\n\treturn canvas;\r\n}\r\n\r\n\r\n/**\r\n* returns the texture file in binary format \r\n* @method toBinary\r\n* @param {Boolean} flip_y\r\n* @return {ArrayBuffer} the arraybuffer of the file containing the image\r\n*/\r\nTexture.binary_extension = \"png\";\r\nTexture.prototype.toBinary = function(flip_y, type)\r\n{\r\n\t//dump to canvas\r\n\tvar canvas = this.toCanvas(null,flip_y);\r\n\t//use the slow method (because its sync)\r\n\tvar data = canvas.toDataURL( type );\r\n\tvar index = data.indexOf(\",\");\r\n\tvar base64_data = data.substr(index+1);\r\n\tvar binStr = atob( base64_data );\r\n\tvar len = binStr.length,\r\n\tarr = new Uint8Array(len);\r\n\tfor (var i=0; i<len; ++i ) {\r\n\t\tarr[i] = binStr.charCodeAt(i);\r\n\t}\r\n\treturn arr;\r\n}\r\n\r\n/**\r\n* returns a Blob containing all the data from the texture\r\n* @method toBlob\r\n* @return {Blob} the blob containing the data\r\n*/\r\nTexture.prototype.toBlob = function(flip_y, type)\r\n{\r\n\tvar arr = this.toBinary( flip_y );\r\n\tvar blob = new Blob( [arr], {type: type || 'image/png'} );\r\n\treturn blob;\r\n}\r\n\r\n//faster depending on the browser\r\nTexture.prototype.toBlobAsync = function(flip_y, type, callback)\r\n{\r\n\t//dump to canvas\r\n\tvar canvas = this.toCanvas(null,flip_y);\r\n\r\n\t//some browser support a fast way to blob a canvas\r\n\tif(canvas.toBlob)\r\n\t{\r\n\t\tcanvas.toBlob( callback, type );\r\n\t\treturn;\r\n\t}\r\n\r\n\t//use the slow method\r\n\tvar blob = this.toBlob( flip_y, type );\r\n\tif(callback)\r\n\t\tcallback(blob);\r\n}\r\n\r\n\r\n/**\r\n* returns a base64 String containing all the data from the texture\r\n* @method toBase64\r\n* @param {boolean} flip_y if you want to flip vertically the image, WebGL saves the images upside down\r\n* @return {String} the data in base64 format\r\n*/\r\nTexture.prototype.toBase64 = function( flip_y )\r\n{\r\n\tvar w = this.width;\r\n\tvar h = this.height;\r\n\r\n\t//Read pixels form WebGL\r\n\tvar buffer = this.getPixels();\r\n\r\n\t//dump to canvas so we can encode it\r\n\tvar canvas = createCanvas(w,h);\r\n\tvar ctx = canvas.getContext(\"2d\");\r\n\tvar pixels = ctx.getImageData(0,0,w,h);\r\n\tpixels.data.set( buffer );\r\n\tctx.putImageData(pixels,0,0);\r\n\r\n\tif(flip_y)\r\n\t{\r\n\t\tvar temp_canvas = createCanvas(w,h);\r\n\t\tvar temp_ctx = temp_canvas.getContext(\"2d\");\r\n\t\ttemp_ctx.translate(0,h);\r\n\t\ttemp_ctx.scale(1,-1);\r\n\t\ttemp_ctx.drawImage( canvas, 0, 0);\r\n\t\tcanvas = temp_canvas;\r\n\t}\r\n\r\n\t//create an image\r\n\tvar img = canvas.toDataURL(\"image/png\"); //base64 string\r\n\treturn img;\r\n}\r\n\r\n/**\r\n* generates some basic metadata about the image\r\n* @method generateMetadata\r\n* @return {Object}\r\n*/\r\nTexture.prototype.generateMetadata = function()\r\n{\r\n\tvar metadata = {};\r\n\tmetadata.width = this.width;\r\n\tmetadata.height = this.height;\r\n\tthis.metadata = metadata;\r\n}\r\n\r\nTexture.compareFormats = function(a,b)\r\n{\r\n\tif(!a || !b) \r\n\t\treturn false;\r\n\tif(a == b) \r\n\t\treturn true;\r\n\r\n\tif( a.width != b.width || \r\n\t\ta.height != b.height || \r\n\t\ta.type != b.type || //gl.UNSIGNED_BYTE\r\n\t\ta.format != b.format || //gl.RGB\r\n\t\ta.texture_type != b.texture_type) //gl.TEXTURE_2D\r\n\t\treturn false;\r\n\treturn true;\r\n}\r\n\r\n/**\r\n* blends texture A and B and stores the result in OUT\r\n* @method blend\r\n* @param {Texture} a\r\n* @param {Texture} b\r\n* @param {Texture} out [optional]\r\n* @return {Object}\r\n*/\r\nTexture.blend = function( a, b, factor, out )\r\n{\r\n\tif(!a || !b) \r\n\t\treturn false;\r\n\tif(a == b) \r\n\t{\r\n\t\tif(out)\r\n\t\t\ta.copyTo(out);\r\n\t\telse\r\n\t\t\ta.toViewport();\r\n\t\treturn true;\r\n\t}\r\n\r\n\tgl.disable( gl.BLEND );\r\n\tgl.disable( gl.DEPTH_TEST );\r\n\tgl.disable( gl.CULL_FACE );\r\n\r\n\tvar shader = GL.Shader.getBlendShader();\r\n\tvar mesh = GL.Mesh.getScreenQuad();\r\n\tb.bind(1);\r\n\tshader.uniforms({u_texture: 0, u_texture2: 1, u_factor: factor});\r\n\r\n\tif(out)\r\n\t{\r\n\t\tout.drawTo( function(){\r\n\t\t\tif(a == out || b == out)\r\n\t\t\t\tthrow(\"Blend output cannot be the same as the input\");\r\n\t\t\ta.bind(0);\r\n\t\t\tshader.draw( mesh, gl.TRIANGLES );\r\n\t\t});\r\n\t\treturn true;\r\n\t}\r\n\r\n\ta.bind(0);\r\n\tshader.draw( mesh, gl.TRIANGLES );\r\n\treturn true;\r\n}\r\n\r\nTexture.cubemapToTexture2D = function( cubemap_texture, size, target_texture, keep_type, yaw )\r\n{\r\n\tif(!cubemap_texture || cubemap_texture.texture_type != gl.TEXTURE_CUBE_MAP) {\r\n\t\tthrow(\"No cubemap in convert\");\r\n\t\treturn null;\r\n\t}\r\n\r\n\tsize = size || cubemap_texture.width;\r\n\tvar type = keep_type ? cubemap_texture.type : gl.UNSIGNED_BYTE;\r\n\tyaw = yaw || 0;\r\n\tif(!target_texture)\r\n\t\ttarget_texture = new GL.Texture(size*2,size,{ minFilter: gl.NEAREST, type: type });\r\n\tvar shader = gl.shaders[\"cubemap_to_texture2D\"];\r\n\tif(!shader)\r\n\t{\r\n\t\tshader = gl.shaders[\"cubemap_to_texture2D\"] = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, '\\\r\n\t\tprecision mediump float;\\n\\\r\n\t\t#define PI 3.14159265358979323846264\\n\\\r\n\t\tuniform samplerCube texture;\\\r\n\t\tvarying vec2 v_coord;\\\r\n\t\tuniform float u_yaw;\\n\\\r\n\t\tvoid main() {\\\r\n\t\t\tfloat alpha = ((1.0 - v_coord.x) * 2.0) * PI + u_yaw;\\\r\n\t\t\tfloat beta = (v_coord.y * 2.0 - 1.0) * PI * 0.5;\\\r\n\t\t\tvec3 N = vec3( -cos(alpha) * cos(beta), sin(beta), sin(alpha) * cos(beta) );\\\r\n\t\t\tgl_FragColor = textureCube(texture,N);\\\r\n\t\t}');\r\n\t}\r\n\tshader.setUniform(\"u_yaw\", yaw );\r\n\ttarget_texture.drawTo(function() {\r\n\t\tgl.disable(gl.DEPTH_TEST);\r\n\t\tgl.disable(gl.CULL_FACE);\r\n\t\tgl.disable(gl.BLEND);\r\n\t\tcubemap_texture.toViewport( shader );\r\n\t});\r\n\treturn target_texture;\r\n}\r\n\r\n/**\r\n* returns a white texture of 1x1 pixel \r\n* @method Texture.getWhiteTexture\r\n* @return {Texture} the white texture\r\n*/\r\nTexture.getWhiteTexture = function( gl )\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar tex = gl.textures[\":white\"];\r\n\tif(tex)\r\n\t\treturn tex;\r\n\r\n\tvar color = new Uint8Array([255,255,255,255]);\r\n\treturn gl.textures[\":white\"] = new GL.Texture(1,1,{ pixel_data: color });\r\n}\r\n\r\n/**\r\n* returns a black texture of 1x1 pixel \r\n* @method Texture.getBlackTexture\r\n* @return {Texture} the black texture\r\n*/\r\nTexture.getBlackTexture = function( gl )\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar tex = gl.textures[\":black\"];\r\n\tif(tex)\r\n\t\treturn tex;\r\n\tvar color = new Uint8Array([0,0,0,255]);\r\n\treturn gl.textures[\":black\"] = new GL.Texture(1,1,{ pixel_data: color });\r\n}\r\n\r\n\r\n/**\r\n* Returns a texture from the texture pool, if none matches the specifications it creates one\r\n* @method Texture.getTemporary\r\n* @param {Number} width the texture width\r\n* @param {Number} height the texture height\r\n* @param {Object|Texture} options to specifiy texture_type,type,format, it can be an object or another texture\r\n* @param {WebGLContext} gl [optional]\r\n* @return {Texture} the textures that matches this settings\r\n*/\r\nTexture.getTemporary = function( width, height, options, gl )\r\n{\r\n\tgl = gl || global.gl;\r\n\r\n\tif(!gl._texture_pool)\r\n\t\tgl._texture_pool = [];\r\n\r\n\tvar result = null;\r\n\r\n\tvar texture_type = GL.TEXTURE_2D;\r\n\tvar type = Texture.DEFAULT_TYPE;\r\n\tvar format = Texture.DEFAULT_FORMAT;\r\n\r\n\tif(options)\r\n\t{\r\n\t\tif(options.texture_type)\r\n\t\t\ttexture_type = options.texture_type;\r\n\t\tif(options.type)\r\n\t\t\ttype = options.type;\r\n\t\tif(options.format)\r\n\t\t\tformat = options.format;\r\n\t}\r\n\r\n\t//var key = (type&0xFFFF) + ((width&0xFFFF)<<16) + ((height&0xFFFF)<<32); // 64bits key: 0x0000 type width height WRONG\r\n\tvar key = texture_type + \":\" + type + \":\" + width + \"x\" + height + \":\" + format;\r\n\r\n\t//iterate\r\n\tvar pool = gl._texture_pool;\r\n\tfor(var i = 0; i < pool.length; ++i)\r\n\t{\r\n\t\tvar tex = pool[i];\r\n\t\tif( tex._key != key ) //|| tex.texture_type != texture_type || tex.format != format )\r\n\t\t\tcontinue;\r\n\t\t//remove from the pool\r\n\t\tpool.splice(i,1); \r\n\t\ttex._pool = 0;\r\n\t\treturn tex; //return\r\n\t}\r\n\r\n\t//not found, create it\r\n\tvar tex = new GL.Texture( width, height, { type: type, texture_type: texture_type, format: format });\r\n\ttex._key = key;\r\n\ttex._pool = 0;\r\n\treturn tex;\r\n}\r\n\r\n/**\r\n* Given a texture it adds it to the texture pool so it can be reused in the future\r\n* @method Texture.releaseTemporary\r\n* @param {GL.Texture} tex\r\n* @param {WebGLContext} gl [optional]\r\n*/\r\n\r\nTexture.releaseTemporary = function( tex, gl )\r\n{\r\n\tgl = gl || global.gl;\r\n\r\n\tif(!gl._texture_pool)\r\n\t\tgl._texture_pool = [];\r\n\r\n\t//if pool is greater than zero means this texture is already inside\r\n\tif( tex._pool > 0 )\r\n\t\tconsole.warn(\"this texture is already in the textures pool\");\r\n\r\n\tvar pool = gl._texture_pool;\r\n\tif(!pool)\r\n\t\tpool = gl._texture_pool = [];\r\n\ttex._pool = getTime();\r\n\tpool.push( tex );\r\n\r\n\t//do not store too much textures in the textures pool\r\n\tif( pool.length > 20 )\r\n\t{\r\n\t\tpool.sort( function(a,b) { return b._pool - a._pool } ); //sort by time\r\n\t\t//pool.sort( function(a,b) { return a._key - b._key } ); //sort by size\r\n\t\tvar tex = pool.pop(); //free the last one\r\n\t\ttex._pool = 0;\r\n\t\ttex.delete();\r\n\t}\r\n}\r\n\r\n//returns the next power of two bigger than size\r\nTexture.nextPOT = function( size )\r\n{\r\n\treturn Math.pow( 2, Math.ceil( Math.log(size) / Math.log(2) ) );\r\n}\r\n\n/** \r\n* FBO for FrameBufferObjects, FBOs are used to store the render inside one or several textures \r\n* Supports multibuffer and depthbuffer texture, useful for deferred rendering\r\n* @namespace GL\r\n* @class FBO\r\n* @param {Array} color_textures an array containing the color textures, if not supplied a render buffer will be used\r\n* @param {GL.Texture} depth_texture the depth texture, if not supplied a render buffer will be used\r\n* @param {Bool} stencil create a stencil buffer?\r\n* @constructor\r\n*/\r\nfunction FBO( textures, depth_texture, stencil, gl )\r\n{\r\n\tgl = gl || global.gl;\r\n\tthis.gl = gl;\r\n\tthis._context_id = gl.context_id; \r\n\r\n\tif(textures && textures.constructor !== Array)\r\n\t\tthrow(\"FBO textures must be an Array\");\r\n\r\n\tthis.handler = null;\r\n\tthis.width = -1;\r\n\tthis.height = -1;\r\n\tthis.color_textures = [];\r\n\tthis.depth_texture = null;\r\n\tthis.stencil = !!stencil;\r\n\r\n\tthis._stencil_enabled = false;\r\n\tthis._num_binded_textures = 0;\r\n\r\n\t//assign textures\r\n\tif((textures && textures.length) || depth_texture)\r\n\t\tthis.setTextures( textures, depth_texture );\r\n\r\n\t//save state\r\n\tthis._old_fbo_handler = null;\r\n\tthis._old_viewport = new Float32Array(4);\r\n\tthis.order = null;\r\n}\r\n\r\nGL.FBO = FBO;\r\n\r\n/**\r\n* Changes the textures binded to this FBO\r\n* @method setTextures\r\n* @param {Array} color_textures an array containing the color textures, if not supplied a render buffer will be used\r\n* @param {GL.Texture} depth_texture the depth texture, if not supplied a render buffer will be used\r\n* @param {Boolean} skip_disable it doenst try to go back to the previous FBO enabled in case there was one\r\n*/\r\nFBO.prototype.setTextures = function( color_textures, depth_texture, skip_disable )\r\n{\r\n\t//test depth\r\n\tif( depth_texture && depth_texture.constructor === GL.Texture )\r\n\t{\r\n\t\tif( depth_texture.format !== GL.DEPTH_COMPONENT && \r\n\t\t\tdepth_texture.format !== GL.DEPTH_STENCIL && \r\n\t\t\tdepth_texture.format !== GL.DEPTH_COMPONENT16 && \r\n\t\t\tdepth_texture.format !== GL.DEPTH_COMPONENT24 &&\r\n\t\t\tdepth_texture.format !== GL.DEPTH_COMPONENT32F )\r\n\t\t\tthrow(\"FBO Depth texture must be of format: gl.DEPTH_COMPONENT, gl.DEPTH_STENCIL or gl.DEPTH_COMPONENT16/24/32F (only in webgl2)\");\r\n\r\n\t\tif( depth_texture.type != GL.UNSIGNED_SHORT && \r\n\t\t\tdepth_texture.type != GL.UNSIGNED_INT && \r\n\t\t\tdepth_texture.type != GL.UNSIGNED_INT_24_8_WEBGL &&\r\n\t\t\tdepth_texture.type != GL.FLOAT)\r\n\t\t\tthrow(\"FBO Depth texture must be of type: gl.UNSIGNED_SHORT, gl.UNSIGNED_INT, gl.UNSIGNED_INT_24_8_WEBGL\");\r\n\t}\r\n\r\n\t//test if is already binded\r\n\tvar same = this.depth_texture == depth_texture;\r\n\tif( same && color_textures )\r\n\t{\r\n\t\tif( color_textures.constructor !== Array )\r\n\t\t\tthrow(\"FBO: color_textures parameter must be an array containing all the textures to be binded in the color\");\r\n\t\tif( color_textures.length == this.color_textures.length )\r\n\t\t{\r\n\t\t\tfor(var i = 0; i < color_textures.length; ++i)\r\n\t\t\t\tif( color_textures[i] != this.color_textures[i] )\r\n\t\t\t\t{\r\n\t\t\t\t\tsame = false;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t\tsame = false;\r\n\t}\r\n\r\n\tif(this._stencil_enabled !== this.stencil)\r\n\t\tsame = false;\r\n\t\t\r\n\tif(same)\r\n\t\treturn;\r\n\r\n\t//copy textures in place\r\n\tthis.color_textures.length = color_textures ? color_textures.length : 0;\r\n\tif(color_textures)\r\n\t\tfor(var i = 0; i < color_textures.length; ++i)\r\n\t\t\tthis.color_textures[i] = color_textures[i];\r\n\tthis.depth_texture = depth_texture;\r\n\r\n\t//update GPU FBO\r\n\tthis.update( skip_disable );\r\n}\r\n\r\n/**\r\n* Updates the FBO with the new set of textures and buffers\r\n* @method update\r\n* @param {Boolean} skip_disable it doenst try to go back to the previous FBO enabled in case there was one\r\n*/\r\nFBO.prototype.update = function( skip_disable )\r\n{\r\n\t//save state to restore afterwards\r\n\tthis._old_fbo_handler = gl.getParameter( gl.FRAMEBUFFER_BINDING );\r\n\r\n\tif(!this.handler)\r\n\t\tthis.handler = gl.createFramebuffer();\r\n\r\n\tvar w = -1,\r\n\t\th = -1,\r\n\t\ttype = null;\r\n\r\n\tvar color_textures = this.color_textures;\r\n\tvar depth_texture = this.depth_texture;\r\n\r\n\t//compute the W and H (and check they have the same size)\r\n\tif(color_textures && color_textures.length)\r\n\t\tfor(var i = 0; i < color_textures.length; i++)\r\n\t\t{\r\n\t\t\tvar t = color_textures[i];\r\n\t\t\tif(t.constructor !== GL.Texture)\r\n\t\t\t\tthrow(\"FBO can only bind instances of GL.Texture\");\r\n\t\t\tif(w == -1) \r\n\t\t\t\tw = t.width;\r\n\t\t\telse if(w != t.width)\r\n\t\t\t\tthrow(\"Cannot bind textures with different dimensions\");\r\n\t\t\tif(h == -1) \r\n\t\t\t\th = t.height;\r\n\t\t\telse if(h != t.height)\r\n\t\t\t\tthrow(\"Cannot bind textures with different dimensions\");\r\n\t\t\tif(type == null) //first one defines the type\r\n\t\t\t\ttype = t.type;\r\n\t\t\telse if (type != t.type)\r\n\t\t\t\tthrow(\"Cannot bind textures to a FBO with different pixel formats\");\r\n\t\t\tif (t.texture_type != gl.TEXTURE_2D)\r\n\t\t\t\tthrow(\"Cannot bind a Cubemap to a FBO\");\r\n\t\t}\r\n\telse\r\n\t{\r\n\t\tw = depth_texture.width;\r\n\t\th = depth_texture.height;\r\n\t}\r\n\r\n\tthis.width = w;\r\n\tthis.height = h;\r\n\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER, this.handler );\r\n\r\n\t//draw_buffers allow to have more than one color texture binded in a FBO\r\n\tvar ext = gl.extensions[\"WEBGL_draw_buffers\"];\r\n\tif( gl.webgl_version == 1 && !ext && color_textures && color_textures.length > 1)\r\n\t\tthrow(\"Rendering to several textures not supported by your browser\");\r\n\r\n\tvar target = gl.webgl_version == 1 ? gl.FRAMEBUFFER : gl.DRAW_FRAMEBUFFER;\r\n\r\n\t//detach anything bindede\r\n\tgl.framebufferRenderbuffer( target, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, null );\r\n\tgl.framebufferRenderbuffer( target, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, null );\r\n\t//detach color too?\r\n\r\n\t//bind a buffer for the depth\r\n\tif( depth_texture && depth_texture.constructor === GL.Texture )\r\n\t{\r\n\t\tif(gl.webgl_version == 1 && !gl.extensions[\"WEBGL_depth_texture\"] )\r\n\t\t\tthrow(\"Rendering to depth texture not supported by your browser\");\r\n\r\n\t\tif(this.stencil && depth_texture.format !== gl.DEPTH_STENCIL )\r\n\t\t\tconsole.warn(\"Stencil cannot be enabled if there is a depth texture with a DEPTH_STENCIL format\");\r\n\r\n\t\tif( depth_texture.format == gl.DEPTH_STENCIL )\r\n\t\t\tgl.framebufferTexture2D( target, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, depth_texture.handler, 0);\r\n\t\telse\r\n\t\t\tgl.framebufferTexture2D( target, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depth_texture.handler, 0);\r\n\t}\r\n\telse //create a renderbuffer to store depth\r\n\t{\r\n\t\tvar depth_renderbuffer = null;\r\n\t\t\r\n\t\t//allows to reuse a renderbuffer between FBOs\r\n\t\tif( depth_texture && depth_texture.constructor === WebGLRenderbuffer && depth_texture.width == w && depth_texture.height == h ) \r\n\t\t\tdepth_renderbuffer = this._depth_renderbuffer = depth_texture;\r\n\t\telse\r\n\t\t{\r\n\t\t\t//create one\r\n\t\t\tdepth_renderbuffer = this._depth_renderbuffer = this._depth_renderbuffer || gl.createRenderbuffer();\r\n\t\t\tdepth_renderbuffer.width = w;\r\n\t\t\tdepth_renderbuffer.height = h;\r\n\t\t}\r\n\t\t\r\n\t\tgl.bindRenderbuffer( gl.RENDERBUFFER, depth_renderbuffer );\r\n\t\tif(this.stencil)\r\n\t\t{\r\n\t\t\tgl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h );\r\n\t\t\tgl.framebufferRenderbuffer( target, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depth_renderbuffer );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tgl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h );\r\n\t\t\tgl.framebufferRenderbuffer( target, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depth_renderbuffer );\r\n\t\t}\r\n\t}\r\n\r\n\t//bind buffers for the colors\r\n\tif(color_textures && color_textures.length)\r\n\t{\r\n\t\tthis.order = []; //draw_buffers request the use of an array with the order of the attachments\r\n\t\tfor(var i = 0; i < color_textures.length; i++)\r\n\t\t{\r\n\t\t\tvar t = color_textures[i];\r\n\r\n\t\t\t//not a bug, gl.COLOR_ATTACHMENT0 + i because COLOR_ATTACHMENT is sequential numbers\r\n\t\t\tgl.framebufferTexture2D( target, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, t.handler, 0 );\r\n\t\t\tthis.order.push( gl.COLOR_ATTACHMENT0 + i );\r\n\t\t}\r\n\t}\r\n\telse //create renderbuffer to store color\r\n\t{\r\n\t\tvar color_renderbuffer = this._color_renderbuffer = this._color_renderbuffer || gl.createRenderbuffer();\r\n\t\tcolor_renderbuffer.width = w;\r\n\t\tcolor_renderbuffer.height = h;\r\n\t\tgl.bindRenderbuffer( gl.RENDERBUFFER, color_renderbuffer );\r\n\t\tgl.renderbufferStorage( gl.RENDERBUFFER, gl.RGBA4, w, h );\r\n\t\tgl.framebufferRenderbuffer( target, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color_renderbuffer );\r\n\t}\r\n\r\n\t//detach old ones (only if is reusing a FBO with a different set of textures)\r\n\tvar num = color_textures ? color_textures.length : 0;\r\n\tfor(var i = num; i < this._num_binded_textures; ++i)\r\n\t\tgl.framebufferTexture2D( target, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, null, 0);\r\n\tthis._num_binded_textures = num;\r\n\r\n\tthis._stencil_enabled = this.stencil;\r\n\r\n\t/* does not work, must be used with the depth_stencil\r\n\tif(this.stencil && !depth_texture)\r\n\t{\r\n\t\tvar stencil_buffer = this._stencil_buffer = this._stencil_buffer || gl.createRenderbuffer();\r\n\t\tstencil_buffer.width = w;\r\n\t\tstencil_buffer.height = h;\r\n\t\tgl.bindRenderbuffer( gl.RENDERBUFFER, stencil_buffer );\r\n\t\tgl.renderbufferStorage( gl.RENDERBUFFER, gl.STENCIL_INDEX8, w, h);\r\n\t\tgl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, stencil_buffer );\r\n\t\tthis._stencil_enabled = true;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis._stencil_buffer = null;\r\n\t\tthis._stencil_enabled = false;\r\n\t}\r\n\t*/\r\n\r\n\t//when using more than one texture you need to use the multidraw extension\r\n\tif(color_textures && color_textures.length > 1)\r\n\t{\r\n\t\tif( ext )\r\n\t\t\text.drawBuffersWEBGL( this.order );\r\n\t\telse\r\n\t\t\tgl.drawBuffers( this.order );\r\n\t}\r\n\r\n\t//check completion\r\n\tvar complete = gl.checkFramebufferStatus( target );\r\n\tif(complete !== gl.FRAMEBUFFER_COMPLETE) //36054: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT\r\n\t\tthrow(\"FBO not complete: \" + complete);\r\n\r\n\t//restore state\r\n\tgl.bindTexture(gl.TEXTURE_2D, null);\r\n\tgl.bindRenderbuffer(gl.RENDERBUFFER, null);\r\n\tif(!skip_disable)\r\n\t\tgl.bindFramebuffer( target, this._old_fbo_handler );\r\n}\r\n\r\n/**\r\n* Enables this FBO (from now on all the render will be stored in the textures attached to this FBO)\r\n* It stores the previous viewport to restore it afterwards, and changes it to full FBO size\r\n* @method bind\r\n* @param {boolean} keep_old keeps the previous FBO is one was attached to restore it afterwards\r\n*/\r\nFBO.prototype.bind = function( keep_old )\r\n{\r\n\tif(!this.color_textures.length && !this.depth_texture)\r\n\t\tthrow(\"FBO: no textures attached to FBO\");\r\n\tthis._old_viewport.set( gl.viewport_data );\r\n\r\n\tif(keep_old)\r\n\t\tthis._old_fbo_handler = gl.getParameter( gl.FRAMEBUFFER_BINDING );\r\n\telse\r\n\t\tthis._old_fbo_handler = null;\r\n\r\n\tif(this._old_fbo_handler != this.handler )\r\n\t\tgl.bindFramebuffer( gl.FRAMEBUFFER, this.handler );\r\n\r\n\t//mark them as in use in the FBO\r\n\tfor(var i = 0; i < this.color_textures.length; ++i)\r\n\t\tthis.color_textures[i]._in_current_fbo = true;\r\n\tif(this.depth_texture)\r\n\t\tthis.depth_texture._in_current_fbo = true;\r\n\r\n\tgl.viewport( 0,0, this.width, this.height );\r\n\tFBO.current = this;\r\n}\r\n\r\n/**\r\n* Disables this FBO, if it was binded with keep_old then the old FBO is enabled, otherwise it will render to the screen\r\n* Restores viewport to previous\r\n* @method unbind\r\n*/\r\nFBO.prototype.unbind = function()\r\n{\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER, this._old_fbo_handler );\r\n\tthis._old_fbo_handler = null;\r\n\tgl.setViewport( this._old_viewport );\r\n\r\n\t//mark the textures as no longer in use\r\n\tfor(var i = 0; i < this.color_textures.length; ++i)\r\n\t\tthis.color_textures[i]._in_current_fbo = false;\r\n\tif(this.depth_texture)\r\n\t\tthis.depth_texture._in_current_fbo = false;\r\n\tFBO.current = null;\r\n}\r\n\r\n//binds another FBO without switch back to previous (faster)\r\nFBO.prototype.switchTo = function( next_fbo )\r\n{\r\n\tnext_fbo._old_fbo_handler = this._old_fbo_handler;\r\n\tnext_fbo._old_viewport.set( this._old_viewport );\r\n\tgl.bindFramebuffer( gl.FRAMEBUFFER, next_fbo.handler );\r\n\tthis._old_fbo_handler = null;\r\n\tgl.viewport( 0,0, this.width, this.height );\r\n\r\n\t//mark the textures as no longer in use\r\n\tfor(var i = 0; i < this.color_textures.length; ++i)\r\n\t\tthis.color_textures[i]._in_current_fbo = false;\r\n\tif(this.depth_texture)\r\n\t\tthis.depth_texture._in_current_fbo = false;\r\n\r\n\t//mark them as in use in the FBO\r\n\tfor(var i = 0; i < next_fbo.color_textures.length; ++i)\r\n\t\tnext_fbo.color_textures[i]._in_current_fbo = true;\r\n\tif(next_fbo.depth_texture)\r\n\t\tnext_fbo.depth_texture._in_current_fbo = true;\r\n\r\n\tFBO.current = next_fbo;\r\n}\r\n\r\nFBO.prototype.delete = function()\r\n{\r\n\tgl.deleteFramebuffer( this.handler );\r\n\tthis.handler = null;\r\n}\r\n\r\n//WebGL 1.0 support for certaing FBOs is not very clear and can crash sometimes\r\nFBO.supported = {};\r\n//type: gl.FLOAT, format: gl.RGBA\r\nFBO.testSupport = function( type, format ) {\r\n\tvar name = type +\":\" + format;\r\n\tif( FBO.supported[ name ] != null )\r\n\t\treturn FBO.supported[ name ];\r\n\r\n\tvar tex = new GL.Texture(1,1,{ format: format, type: type });\r\n\ttry\r\n\t{\r\n\t\tvar fbo = new GL.FBO([tex]);\r\n\t}\r\n\tcatch (err)\r\n\t{\r\n\t\tconsole.warn(\"This browser WEBGL implementation doesn't support this FBO format: \" + GL.reverse[type] + \" \" + GL.reverse[format] );\r\n\t\treturn FBO.supported[ name ] = false;\r\n\t}\r\n\tFBO.supported[ name ] = true;\r\n\treturn true;\r\n}\r\n\r\nFBO.prototype.toSingle = function()\r\n{\r\n\tif( this.color_textures.length < 2 )\r\n\t\treturn; //nothing to do\r\n\tvar ext = gl.extensions.WEBGL_draw_buffers;\r\n\tif( ext )\r\n\t\text.drawBuffersWEBGL( [ this.order[0] ] );\r\n\telse\r\n\t\tgl.drawBuffers( [ this.order[0] ] );\r\n}\r\n\r\nFBO.prototype.toMulti = function()\r\n{\r\n\tif( this.color_textures.length < 2 )\r\n\t\treturn; //nothing to do\r\n\tvar ext = gl.extensions.WEBGL_draw_buffers;\r\n\tif( ext )\r\n\t\text.drawBuffersWEBGL( this.order );\r\n\telse\r\n\t\tgl.drawBuffers( this.order );\r\n}\r\n\r\n//clears only the secondary buffers (not the main one)\r\nFBO.prototype.clearSecondary = function( color )\r\n{\r\n\tif(!this.order || this.order.length < 2)\r\n\t\treturn;\r\n\r\n\tvar ext = gl.extensions.WEBGL_draw_buffers;\r\n\tvar new_order = [gl.NONE];\r\n\tfor(var i = 1; i < this.order.length; ++i)\r\n\t\tnew_order.push(this.order[i]);\r\n\tif(ext)\r\n\t\text.drawBuffersWEBGL( new_order );\r\n\telse\r\n\t\tgl.drawBuffers( new_order );\r\n\tgl.clearColor( color[0],color[1],color[2],color[3] );\r\n\tgl.clear( gl.COLOR_BUFFER_BIT );\r\n\r\n\tif(ext)\r\n\t\text.drawBuffersWEBGL( this.order );\r\n\telse\r\n\t\tgl.drawBuffers( this.order );\r\n}\r\n\r\n\n\r\n/**\r\n* @namespace GL\r\n*/\r\n\r\n/**\r\n* Shader class to upload programs to the GPU\r\n* @class Shader\r\n* @constructor\r\n* @param {String} vertexSource (it also allows to pass a compiled vertex shader)\r\n* @param {String} fragmentSource (it also allows to pass a compiled fragment shader)\r\n* @param {Object} macros (optional) precompiler macros to be applied when compiling\r\n*/\r\nglobal.Shader = GL.Shader = function Shader( vertexSource, fragmentSource, macros )\r\n{\r\n\tif(GL.debug)\r\n\t\tconsole.log(\"GL.Shader created\");\r\n\r\n\tif( !vertexSource || !fragmentSource )\r\n\t\tthrow(\"GL.Shader source code parameter missing\");\r\n\r\n\t//used to avoid problems with resources moving between different webgl context\r\n\tthis._context_id = global.gl.context_id; \r\n\tvar gl = this.gl = global.gl;\r\n\r\n\t//expand macros\r\n\tvar extra_code = Shader.expandMacros( macros );\r\n\r\n\tvar final_vertexSource = vertexSource.constructor === String ? Shader.injectCode( extra_code, vertexSource, gl ) : vertexSource;\r\n\tvar final_fragmentSource = fragmentSource.constructor === String ? Shader.injectCode( extra_code, fragmentSource, gl ) : fragmentSource;\r\n\r\n\tthis.program = gl.createProgram();\r\n\r\n\tvar vs = vertexSource.constructor === String ? GL.Shader.compileSource( gl.VERTEX_SHADER, final_vertexSource ) : vertexSource;\r\n\tvar fs = fragmentSource.constructor === String ? GL.Shader.compileSource( gl.FRAGMENT_SHADER, final_fragmentSource ) : fragmentSource;\r\n\r\n\tgl.attachShader( this.program, vs, gl );\r\n\tgl.attachShader( this.program, fs, gl );\r\n\tgl.linkProgram(this.program);\r\n\tif (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {\r\n\t\tthrow 'link error: ' + gl.getProgramInfoLog(this.program);\r\n\t}\r\n\r\n\tthis.vs_shader = vs;\r\n\tthis.fs_shader = fs;\r\n\r\n\t//Extract info from the shader\r\n\tthis.attributes = {}; \r\n\tthis.uniformInfo = {};\r\n\tthis.samplers = {};\r\n\r\n\t//extract info about the shader to speed up future processes\r\n\tthis.extractShaderInfo();\r\n}\r\n\r\nShader.expandMacros = function(macros)\r\n{\r\n\tvar extra_code = \"\"; //add here preprocessor directives that should be above everything\r\n\tif(macros)\r\n\t\tfor(var i in macros)\r\n\t\t\textra_code += \"#define \" + i + \" \" + (macros[i] ? macros[i] : \"\") + \"\\n\";\r\n\treturn extra_code;\r\n}\r\n\r\n//this is done to avoid problems with the #version which must be in the first line\r\nShader.injectCode = function( inject_code, code, gl )\r\n{\r\n\tvar index = code.indexOf(\"\\n\");\r\n\tvar version = ( gl ? \"#define WEBGL\" + gl.webgl_version + \"\\n\" : \"\");\r\n\tvar first_line = code.substr(0,index).trim();\r\n\tif( first_line.indexOf(\"#version\") == -1 )\r\n\t\treturn version + inject_code + code;\r\n\treturn first_line + \"\\n\" + version + inject_code + code.substr(index);\r\n}\r\n\r\n\r\n/**\r\n* Compiles one single shader source (could be gl.VERTEX_SHADER or gl.FRAGMENT_SHADER) and returns the webgl shader handler \r\n* Used internaly to compile the vertex and fragment shader.\r\n* It throws an exception if there is any error in the code\r\n* @method Shader.compileSource\r\n* @param {Number} type could be gl.VERTEX_SHADER or gl.FRAGMENT_SHADER\r\n* @param {String} source the source file to compile\r\n* @return {WebGLShader} the handler from webgl\r\n*/\r\nShader.compileSource = function( type, source, gl, shader )\r\n{\r\n\tgl = gl || global.gl;\r\n\tshader = shader || gl.createShader(type);\r\n\tgl.shaderSource(shader, source);\r\n\tgl.compileShader(shader);\r\n\tif (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\r\n\t\tthrow (type == gl.VERTEX_SHADER ? \"Vertex\" : \"Fragment\") + ' shader compile error: ' + gl.getShaderInfoLog(shader);\r\n\t}\r\n\treturn shader;\r\n}\r\n\r\nShader.parseError = function( error_str, vs_code, fs_code )\r\n{\r\n\tif(!error_str)\r\n\t\treturn null;\r\n\r\n\tvar t = error_str.split(\" \");\r\n\tvar nums = t[5].split(\":\");\r\n\r\n\treturn {\r\n\t\ttype: t[0],\r\n\t\tline_number: parseInt( nums[1] ),\r\n\t\tline_pos: parseInt( nums[0] ),\r\n\t\tline_code: ( t[0] == \"Fragment\" ? fs_code : vs_code ).split(\"\\n\")[ parseInt( nums[1] ) ],\r\n\t\terr: error_str\r\n\t};\r\n}\r\n\r\n/**\r\n* It updates the code inside one shader\r\n* @method updateShader\r\n* @param {String} vertexSource \r\n* @param {String} fragmentSource \r\n* @param {Object} macros [optional]\r\n*/\r\nShader.prototype.updateShader = function( vertexSource, fragmentSource, macros )\r\n{\r\n\tvar gl = this.gl || global.gl;\r\n\r\n\t//expand macros\r\n\tvar extra_code = Shader.expandMacros( macros );\r\n\r\n\tif(!this.program)\r\n\t\tthis.program = gl.createProgram();\r\n\telse\r\n\t{\r\n\t\tgl.detachShader( this.program, this.vs_shader );\r\n\t\tgl.detachShader( this.program, this.fs_shader );\r\n\t}\r\n\r\n\tvar extra_code = Shader.expandMacros( macros );\r\n\r\n\tvar final_vertexSource = vertexSource.constructor === String ? Shader.injectCode( extra_code, vertexSource, gl ) : vertexSource;\r\n\tvar final_fragmentSource = fragmentSource.constructor === String ? Shader.injectCode( extra_code, fragmentSource, gl ) : fragmentSource;\r\n\r\n\tvar vs = vertexSource.constructor === String ? GL.Shader.compileSource( gl.VERTEX_SHADER, final_vertexSource ) : vertexSource;\r\n\tvar fs = fragmentSource.constructor === String ? GL.Shader.compileSource( gl.FRAGMENT_SHADER, final_fragmentSource ) : fragmentSource;\r\n\r\n\tgl.attachShader( this.program, vs, gl );\r\n\tgl.attachShader( this.program, fs, gl );\r\n\tgl.linkProgram( this.program );\r\n\tif (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {\r\n\t\tthrow 'link error: ' + gl.getProgramInfoLog( this.program );\r\n\t}\r\n\r\n\t//store shaders separated\r\n\tthis.vs_shader = vs;\r\n\tthis.fs_shader = fs;\r\n\r\n\t//Extract info from the shader\r\n\tthis.attributes = {}; \r\n\tthis.uniformInfo = {};\r\n\tthis.samplers = {};\r\n\r\n\t//extract info about the shader to speed up future processes\r\n\tthis.extractShaderInfo();\r\n}\r\n\r\n/**\r\n* It extract all the info about the compiled shader program, all the info about uniforms and attributes.\r\n* This info is stored so it works faster during rendering.\r\n* @method extractShaderInfo\r\n*/\r\n\r\n\r\nShader.prototype.extractShaderInfo = function()\r\n{\r\n\tvar gl = this.gl;\r\n\t\r\n\tvar l = gl.getProgramParameter( this.program, gl.ACTIVE_UNIFORMS );\r\n\r\n\t//extract uniforms info\r\n\tfor(var i = 0; i < l; ++i)\r\n\t{\r\n\t\tvar data = gl.getActiveUniform( this.program, i);\r\n\t\tif(!data) break;\r\n\r\n\t\tvar uniformName = data.name;\r\n\r\n\t\t//arrays have uniformName[0], strip the [] (also data.size tells you if it is an array)\r\n\t\tvar pos = uniformName.indexOf(\"[\"); \r\n\t\tif(pos != -1)\r\n\t\t{\r\n\t\t\tvar pos2 = uniformName.indexOf(\"].\"); //leave array of structs though\r\n\t\t\tif(pos2 == -1)\r\n\t\t\t\tuniformName = uniformName.substr(0,pos);\r\n\t\t}\r\n\r\n\t\t//store texture samplers\r\n\t\tif(data.type == gl.SAMPLER_2D || data.type == gl.SAMPLER_CUBE || data.type == GL.SAMPLER_3D)\r\n\t\t\tthis.samplers[ uniformName ] = data.type;\r\n\t\t\r\n\t\t//get which function to call when uploading this uniform\r\n\t\tvar func = Shader.getUniformFunc(data);\r\n\t\tvar is_matrix = false;\r\n\t\tif(data.type == gl.FLOAT_MAT2 || data.type == gl.FLOAT_MAT3 || data.type == gl.FLOAT_MAT4)\r\n\t\t\tis_matrix = true;\r\n\t\tvar type_length = GL.TYPE_LENGTH[ data.type ] || 1;\r\n\r\n\t\t//save the info so the user doesnt have to specify types when uploading data to the shader\r\n\t\tthis.uniformInfo[ uniformName ] = { \r\n\t\t\ttype: data.type,\r\n\t\t\tfunc: func,\r\n\t\t\tsize: data.size,\r\n\t\t\ttype_length: type_length,\r\n\t\t\tis_matrix: is_matrix,\r\n\t\t\tloc: gl.getUniformLocation(this.program, uniformName),\r\n\t\t\tdata: new Float32Array( type_length * data.size ) //prealloc space to assign uniforms that are not typed\r\n\t\t};\r\n\t}\r\n\r\n\t//extract attributes info\r\n\tfor(var i = 0, l = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES); i < l; ++i)\r\n\t{\r\n\t\tvar data = gl.getActiveAttrib( this.program, i);\r\n\t\tif(!data)\r\n\t\t\tbreak;\r\n\t\tvar func = Shader.getUniformFunc(data);\r\n\t\tvar type_length = GL.TYPE_LENGTH[ data.type ] || 1;\r\n\t\tthis.uniformInfo[ data.name ] = { \r\n\t\t\ttype: data.type,\r\n\t\t\tfunc: func,\r\n\t\t\ttype_length: type_length,\r\n\t\t\tsize: data.size,\r\n\t\t\tloc: null \r\n\t\t}; //gl.getAttribLocation( this.program, data.name )\r\n\t\tthis.attributes[ data.name ] = gl.getAttribLocation(this.program, data.name );\t\r\n\t}\r\n}\r\n\r\n/**\r\n* Returns if this shader has a uniform with the given name\r\n* @method hasUniform\r\n* @param {String} name name of the uniform\r\n* @return {Boolean}\r\n*/\r\nShader.prototype.hasUniform = function(name)\r\n{\r\n\treturn this.uniformInfo[name];\r\n}\r\n\r\n/**\r\n* Returns if this shader has an attribute with the given name\r\n* @method hasAttribute\r\n* @param {String} name name of the attribute\r\n* @return {Boolean}\r\n*/\r\nShader.prototype.hasAttribute = function(name)\r\n{\r\n\treturn this.attributes[name];\r\n}\r\n\r\n\r\n/**\r\n* Tells you which function to call when uploading a uniform according to the data type in the shader\r\n* Used internally from extractShaderInfo to optimize calls \r\n* @method Shader.getUniformFunc\r\n* @param {Object} data info about the uniform\r\n* @return {Function}\r\n*/\r\nShader.getUniformFunc = function( data )\r\n{\r\n\tvar func = null;\r\n\tswitch (data.type)\r\n\t{\r\n\t\tcase GL.FLOAT: \t\t\r\n\t\t\tif(data.size == 1)\r\n\t\t\t\tfunc = gl.uniform1f; \r\n\t\t\telse\r\n\t\t\t\tfunc = gl.uniform1fv; \r\n\t\t\tbreak;\r\n\t\tcase GL.FLOAT_MAT2: func = gl.uniformMatrix2fv; break;\r\n\t\tcase GL.FLOAT_MAT3:\tfunc = gl.uniformMatrix3fv; break;\r\n\t\tcase GL.FLOAT_MAT4:\tfunc = gl.uniformMatrix4fv; break;\r\n\t\tcase GL.FLOAT_VEC2: func = gl.uniform2fv; break;\r\n\t\tcase GL.FLOAT_VEC3: func = gl.uniform3fv; break;\r\n\t\tcase GL.FLOAT_VEC4: func = gl.uniform4fv; break;\r\n\r\n\t\tcase GL.UNSIGNED_INT: \r\n\t\tcase GL.INT: \t  \r\n\t\t\tif(data.size == 1)\r\n\t\t\t\tfunc = gl.uniform1i; \r\n\t\t\telse\r\n\t\t\t\tfunc = gl.uniform1iv; \r\n\t\t\tbreak;\r\n\t\tcase GL.INT_VEC2: func = gl.uniform2iv; break;\r\n\t\tcase GL.INT_VEC3: func = gl.uniform3iv; break;\r\n\t\tcase GL.INT_VEC4: func = gl.uniform4iv; break;\r\n\r\n\t\tcase GL.SAMPLER_2D:\r\n\t\tcase GL.SAMPLER_3D:\r\n\t\tcase GL.SAMPLER_CUBE:\r\n\t\t\tfunc = gl.uniform1i; break;\r\n\t\tdefault: func = gl.uniform1f; break;\r\n\t}\t\r\n\treturn func;\r\n}\r\n\r\n/**\r\n* Create a shader from two urls. While the system is fetching the two urls, the shader contains a dummy shader that renders black.\r\n* @method Shader.fromURL\r\n* @param {String} vs_path the url to the vertex shader\r\n* @param {String} fs_path the url to the fragment shader\r\n* @param {Function} on_complete [Optional] a callback to call once the shader is ready.\r\n* @return {Shader}\r\n*/\r\nShader.fromURL = function( vs_path, fs_path, on_complete )\r\n{\r\n\t//create simple shader first\r\n\tvar vs_code = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tattribute vec3 a_vertex;\\n\\\r\n\t\t\tattribute mat4 u_mvp;\\n\\\r\n\t\t\tvoid main() { \\n\\\r\n\t\t\t\tgl_Position = u_mvp * vec4(a_vertex,1.0); \\n\\\r\n\t\t\t}\\n\\\r\n\t\t\";\r\n\tvar fs_code = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tgl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\t\r\n\tvar shader = new GL.Shader(vs_code, fs_code);\r\n\tshader.ready = false;\r\n\r\n\tvar true_vs = null;\r\n\tvar true_fs = null;\r\n\r\n\tHttpRequest( vs_path, null, function(vs_code) {\r\n\t\ttrue_vs = vs_code;\r\n\t\tif(true_fs)\r\n\t\t\tcompileShader();\r\n\t});\r\n\r\n\tHttpRequest( fs_path, null, function(fs_code) {\r\n\t\ttrue_fs = fs_code;\r\n\t\tif(true_vs)\r\n\t\t\tcompileShader();\r\n\t});\r\n\r\n\tfunction compileShader()\r\n\t{\r\n\t\tvar true_shader = new GL.Shader(true_vs, true_fs);\r\n\t\tfor(var i in true_shader)\r\n\t\t\tshader[i] = true_shader[i];\r\n\t\tshader.ready = true;\r\n\t}\r\n\r\n\treturn shader;\r\n}\r\n\r\n/**\r\n* enables the shader (calls useProgram)\r\n* @method bind\r\n*/\r\nShader.prototype.bind = function()\r\n{\r\n\tvar gl = this.gl;\r\n\tgl.useProgram( this.program );\r\n\tgl._current_shader = this;\r\n}\r\n\r\n/**\r\n* Returns the location of a uniform or attribute\r\n* @method getLocation\r\n* @param {String} name\r\n* @return {WebGLUniformLocation} location\r\n*/\r\nShader.prototype.getLocation = function( name )\r\n{\r\n\tvar info = this.uniformInfo[name];\r\n\tif(info)\r\n\t\treturn this.uniformInfo[name].loc;\r\n\treturn null;\r\n}\r\n\r\n/**\r\n* Uploads a set of uniforms to the Shader. You dont need to specify types, they are infered from the shader info.\r\n* @method uniforms\r\n* @param {Object} uniforms\r\n*/\r\nShader._temp_uniform = new Float32Array(16);\r\n\r\nShader.prototype.uniforms = function(uniforms) {\r\n\tvar gl = this.gl;\r\n\tgl.useProgram(this.program);\r\n\tgl._current_shader = this;\r\n\r\n\tfor (var name in uniforms)\r\n\t{\r\n\t\tvar info = this.uniformInfo[ name ];\r\n\t\tif (!info)\r\n\t\t\tcontinue;\r\n\t\tthis._setUniform( name, uniforms[name] );\r\n\t\t//this.setUniform( name, uniforms[name] );\r\n\t\t//this._assing_uniform(uniforms, name, gl );\r\n\t}\r\n\r\n\treturn this;\r\n}//uniforms\r\n\r\nShader.prototype.uniformsArray = function(array) {\r\n\tvar gl = this.gl;\r\n\tgl.useProgram( this.program );\r\n\tgl._current_shader = this;\r\n\r\n\tfor(var i = 0, l = array.length; i < l; ++i)\r\n\t{\r\n\t\tvar uniforms = array[i];\r\n\t\tfor (var name in uniforms)\r\n\t\t\tthis._setUniform( name, uniforms[name] );\r\n\t\t\t//this._assing_uniform(uniforms, name, gl );\r\n\t}\r\n\r\n\treturn this;\r\n}\r\n\r\n/**\r\n* Uploads a uniform to the Shader. You dont need to specify types, they are infered from the shader info. Shader must be binded!\r\n* @method setUniform\r\n* @param {string} name\r\n* @param {*} value\r\n*/\r\nShader.prototype.setUniform = (function(){\r\n\r\n\treturn (function(name, value)\r\n\t{\r\n\t\tif(\tthis.gl._current_shader != this )\r\n\t\t\tthis.bind();\r\n\r\n\t\tvar info = this.uniformInfo[name];\r\n\t\tif (!info)\r\n\t\t\treturn;\r\n\r\n\t\tif(info.loc === null)\r\n\t\t\treturn;\r\n\r\n\t\tif(value == null) //strict?\r\n\t\t\treturn;\r\n\r\n\t\tif(value.constructor === Array)\r\n\t\t{\r\n\t\t\tinfo.data.set( value );\r\n\t\t\tvalue = info.data;\r\n\t\t}\r\n\r\n\t\tif(info.is_matrix)\r\n\t\t\tinfo.func.call( this.gl, info.loc, false, value );\r\n\t\telse\r\n\t\t\tinfo.func.call( this.gl, info.loc, value );\r\n\t});\r\n})();\r\n\r\n//skips enabling shader\r\nShader.prototype._setUniform = (function(){\r\n\r\n\treturn (function(name, value)\r\n\t{\r\n\t\tvar info = this.uniformInfo[ name ];\r\n\t\tif (!info)\r\n\t\t\treturn;\r\n\r\n\t\tif(info.loc === null)\r\n\t\t\treturn;\r\n\r\n\t\t//if(info.loc.constructor !== Function)\r\n\t\t//\treturn;\r\n\r\n\t\tif(value == null) \r\n\t\t\treturn;\r\n\r\n\t\tif(value.constructor === Array)\r\n\t\t{\r\n\t\t\tinfo.data.set( value );\r\n\t\t\tvalue = info.data;\r\n\t\t}\r\n\r\n\t\tif(info.is_matrix)\r\n\t\t\tinfo.func.call( this.gl, info.loc, false, value );\r\n\t\telse\r\n\t\t\tinfo.func.call( this.gl, info.loc, value );\r\n\t});\r\n})();\r\n\r\n/**\r\n* Renders a mesh using this shader, remember to use the function uniforms before to enable the shader\r\n* @method draw\r\n* @param {Mesh} mesh\r\n* @param {number} mode could be gl.LINES, gl.POINTS, gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN\r\n* @param {String} index_buffer_name the name of the index buffer, if not provided triangles will be assumed\r\n*/\r\nShader.prototype.draw = function( mesh, mode, index_buffer_name ) {\r\n\tindex_buffer_name = index_buffer_name === undefined ? (mode == gl.LINES ? 'lines' : 'triangles') : index_buffer_name;\r\n\tthis.drawBuffers( mesh.vertexBuffers,\r\n\t  index_buffer_name ? mesh.indexBuffers[ index_buffer_name ] : null,\r\n\t  arguments.length < 2 ? gl.TRIANGLES : mode);\r\n}\r\n\r\n/**\r\n* Renders a range of a mesh using this shader\r\n* @method drawRange\r\n* @param {Mesh} mesh\r\n* @param {number} mode could be gl.LINES, gl.POINTS, gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN\r\n* @param {number} start first primitive to render\r\n* @param {number} length number of primitives to render\r\n* @param {String} index_buffer_name the name of the index buffer, if not provided triangles will be assumed\r\n*/\r\nShader.prototype.drawRange = function(mesh, mode, start, length, index_buffer_name )\r\n{\r\n\tindex_buffer_name = index_buffer_name === undefined ? (mode == gl.LINES ? 'lines' : 'triangles') : index_buffer_name;\r\n\r\n\tthis.drawBuffers( mesh.vertexBuffers,\r\n\t  index_buffer_name ? mesh.indexBuffers[ index_buffer_name ] : null,\r\n\t  mode, start, length);\r\n}\r\n\r\n/**\r\n* render several buffers with a given index buffer\r\n* @method drawBuffers\r\n* @param {Object} vertexBuffers an object containing all the buffers\r\n* @param {IndexBuffer} indexBuffer\r\n* @param {number} mode could be gl.LINES, gl.POINTS, gl.TRIANGLES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN\r\n* @param {number} range_start first primitive to render\r\n* @param {number} range_length number of primitives to render\r\n*/\r\n\r\n//this two variables are a hack to avoid memory allocation on drawCalls\r\nvar temp_attribs_array = new Uint8Array(16);\r\nvar temp_attribs_array_zero = new Uint8Array(16); //should be filled with zeros always\r\n\r\nShader.prototype.drawBuffers = function( vertexBuffers, indexBuffer, mode, range_start, range_length )\r\n{\r\n\tif(range_length == 0)\r\n\t\treturn;\r\n\r\n\tvar gl = this.gl;\r\n\r\n\tgl.useProgram(this.program); //this could be removed assuming every shader is called with some uniforms \r\n\r\n\t// enable attributes as necessary.\r\n\tvar length = 0;\r\n\tvar attribs_in_use = temp_attribs_array; //hack to avoid garbage\r\n\tattribs_in_use.set( temp_attribs_array_zero ); //reset\r\n\r\n\tfor (var name in vertexBuffers)\r\n\t{\r\n\t\tvar buffer = vertexBuffers[name];\r\n\t\tvar attribute = buffer.attribute || name;\r\n\t\t//precompute attribute locations in shader\r\n\t\tvar location = this.attributes[attribute];// || gl.getAttribLocation(this.program, attribute);\r\n\r\n\t\tif (location == null || !buffer.buffer) //-1 changed for null\r\n\t\t\tcontinue; //ignore this buffer\r\n\r\n\t\tattribs_in_use[location] = 1; //mark it as used\r\n\r\n\t\t//this.attributes[attribute] = location;\r\n\t\tgl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);\r\n\t\tgl.enableVertexAttribArray(location);\r\n\r\n\t\tgl.vertexAttribPointer(location, buffer.buffer.spacing, buffer.buffer.gl_type, false, 0, 0);\r\n\t\tlength = buffer.buffer.length / buffer.buffer.spacing;\r\n\t}\r\n\r\n\t//range rendering\r\n\tvar offset = 0; //in bytes\r\n\tif(range_start > 0) //render a polygon range\r\n\t\toffset = range_start; //in bytes (Uint16 == 2 bytes)\r\n\r\n\tif (indexBuffer)\r\n\t\tlength = indexBuffer.buffer.length - offset;\r\n\r\n\tif(range_length > 0 && range_length < length) //to avoid problems\r\n\t\tlength = range_length;\r\n\r\n\tvar BYTES_PER_ELEMENT = (indexBuffer && indexBuffer.data) ? indexBuffer.data.constructor.BYTES_PER_ELEMENT : 1;\r\n\toffset *= BYTES_PER_ELEMENT;\r\n\r\n\t// Force to disable buffers in this shader that are not in this mesh\r\n\tfor (var attribute in this.attributes)\r\n\t{\r\n\t\tvar location = this.attributes[attribute];\r\n\t\tif (!(attribs_in_use[location])) {\r\n\t\t\tgl.disableVertexAttribArray(this.attributes[attribute]);\r\n\t\t}\r\n\t}\r\n\r\n\t// Draw the geometry.\r\n\tif (length && (!indexBuffer || indexBuffer.buffer)) {\r\n\t  if (indexBuffer) {\r\n\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer);\r\n\t\tgl.drawElements( mode, length, indexBuffer.buffer.gl_type, offset); //gl.UNSIGNED_SHORT\r\n\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);\r\n\t  } else {\r\n\t\tgl.drawArrays(mode, offset, length);\r\n\t  }\r\n\t}\r\n\r\n\treturn this;\r\n}\r\n\r\nShader._instancing_arrays = [];\r\n\r\nShader.prototype.drawInstanced = function( mesh, primitive, indices, instanced_uniforms, range_start, range_length, num_instances )\r\n{\r\n\tif(range_length === 0)\r\n\t\treturn;\r\n\r\n\t//bind buffers\r\n\tvar gl = this.gl;\r\n\r\n\tif( gl.webgl_version == 1 && !gl.extensions.ANGLE_instanced_arrays )\r\n\t\tthrow(\"instancing not supported\");\r\n\r\n\tgl.useProgram(this.program); //this could be removed assuming every shader is called with some uniforms \r\n\r\n\t// enable attributes as necessary.\r\n\tvar length = 0;\r\n\tvar attribs_in_use = temp_attribs_array; //hack to avoid garbage\r\n\tattribs_in_use.set( temp_attribs_array_zero ); //reset\r\n\r\n\tvar vertexBuffers = mesh.vertexBuffers;\r\n\r\n\tfor (var name in vertexBuffers)\r\n\t{\r\n\t\tvar buffer = vertexBuffers[name];\r\n\t\tvar attribute = buffer.attribute || name;\r\n\t\t//precompute attribute locations in shader\r\n\t\tvar location = this.attributes[attribute];// || gl.getAttribLocation(this.program, attribute);\r\n\r\n\t\tif (location == null || !buffer.buffer) //-1 changed for null\r\n\t\t\tcontinue; //ignore this buffer\r\n\r\n\t\tattribs_in_use[location] = 1; //mark it as used\r\n\r\n\t\t//this.attributes[attribute] = location;\r\n\t\tgl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);\r\n\t\tgl.enableVertexAttribArray(location);\r\n\r\n\t\tgl.vertexAttribPointer(location, buffer.buffer.spacing, buffer.buffer.gl_type, false, 0, 0);\r\n\t\tlength = buffer.buffer.length / buffer.buffer.spacing;\r\n\t}\r\n\r\n\tvar indexBuffer = null;\r\n\tif(indices)\r\n\t{\r\n\t\tif(indices.constructor === GL.Buffer)\r\n\t\t\tindexBuffer = indices;\r\n\t\telse\r\n\t\t\tindexBuffer = mesh.getIndexBuffer( indices );\r\n\t}\r\n\r\n\t//range rendering\r\n\tvar offset = 0; //in bytes\r\n\tif(range_start > 0) //render a polygon range\r\n\t\toffset = range_start; \r\n\r\n\tif (indexBuffer)\r\n\t\tlength = indexBuffer.buffer.length - offset;\r\n\r\n\tif(range_length > 0 && range_length < length) //to avoid problems\r\n\t\tlength = range_length;\r\n\r\n\tvar BYTES_PER_ELEMENT = (indexBuffer && indexBuffer.data) ? indexBuffer.data.constructor.BYTES_PER_ELEMENT : 1;\r\n\toffset *= BYTES_PER_ELEMENT;\r\n\r\n\t// Force to disable buffers in this shader that are not in this mesh\r\n\tfor (var attribute in this.attributes)\r\n\t{\r\n\t\tvar location = this.attributes[attribute];\r\n\t\tif (!(attribs_in_use[location])) {\r\n\t\t\tgl.disableVertexAttribArray(this.attributes[attribute]);\r\n\t\t}\r\n\t}\r\n\r\n\tvar ext = gl.extensions.ANGLE_instanced_arrays;\r\n\tvar batch_length = 0;\r\n\r\n\t//pack the instanced uniforms\r\n\tvar index = 0;\r\n\tfor(var uniform in instanced_uniforms)\r\n\t{\r\n\t\tvar values = instanced_uniforms[ uniform ];\r\n\t\tbatch_length = values.length;\r\n\t\tvar uniformLocation = this.attributes[ uniform ];\r\n\t\tif( uniformLocation == null )\r\n\t\t\treturn; //not found\r\n\t\tvar element_size = 0;\r\n\t\tvar total_size = 0;\r\n\t\tif( values.constructor === Array )\r\n\t\t{\r\n\t\t\telement_size = values[0].constructor === Number ? 1 : values[0].length;\r\n\t\t\ttotal_size = element_size * values.length;\r\n\t\t}\r\n\t\telse //typed array\r\n\t\t{\r\n\t\t\telement_size = this.uniformInfo[ uniform ].type_length;\r\n\t\t\ttotal_size = values.length;\r\n\t\t\tbatch_length = total_size / element_size;\r\n\t\t}\r\n\r\n\t\tvar data_array = Shader._instancing_arrays[ index ];\r\n\t\tif( !data_array || data_array.data.length < total_size )\r\n\t\t\tdata_array = Shader._instancing_arrays[ index ] = { data: new Float32Array( total_size ), buffer: gl.createBuffer() };\r\n\t\tdata_array.uniform = uniform;\r\n\t\tdata_array.element_size = element_size;\r\n\t\tif( values.constructor === Array )\r\n\t\t\tfor(var j = 0; j < values.length; ++j)\r\n\t\t\t\tdata_array.data.set( values[j], j*element_size ); //flatten array\r\n\t\telse\r\n\t\t\tdata_array.data.set( values ); //copy\r\n\t\tgl.bindBuffer( gl.ARRAY_BUFFER, data_array.buffer );\r\n\t\tgl.bufferData( gl.ARRAY_BUFFER, data_array.data, gl.STREAM_DRAW );\r\n\r\n\t\tif(element_size == 16) //mat4\r\n\t\t{\r\n\t\t\tfor(var k = 0; k < 4; ++k)\r\n\t\t\t{\r\n\t\t\t\tgl.enableVertexAttribArray( uniformLocation+k );\r\n\t\t\t\tgl.vertexAttribPointer( uniformLocation+k, 4, gl.FLOAT , false, 16*4, k*4*4 ); //4 bytes per float\r\n\t\t\t\tif( ext ) //webgl 1\r\n\t\t\t\t\text.vertexAttribDivisorANGLE( uniformLocation+k, 1 ); // This makes it instanced!\r\n\t\t\t\telse\r\n\t\t\t\t\tgl.vertexAttribDivisor( uniformLocation+k, 1 ); // This makes it instanced!\r\n\t\t\t}\r\n\t\t}\r\n\t\telse //others\r\n\t\t{\r\n\t\t\tgl.enableVertexAttribArray( uniformLocation );\r\n\t\t\tgl.vertexAttribPointer( uniformLocation, element_size, gl.FLOAT, false, element_size*4, 0 ); //4 bytes per float, 0 offset\r\n\t\t\tif( ext ) //webgl 1\r\n\t\t\t\text.vertexAttribDivisorANGLE( uniformLocation, 1 ); // This makes it instanced!\r\n\t\t\telse\r\n\t\t\t\tgl.vertexAttribDivisor( uniformLocation, 1 ); // This makes it instanced!\r\n\t\t}\r\n\t\tindex+=1;\r\n\t}\r\n\r\n\tif( num_instances )\r\n\t\tbatch_length = num_instances;\r\n\r\n\tif( ext ) //webgl 1.0\r\n\t{\r\n\t\tif(indexBuffer)\r\n\t\t{\r\n\t\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer);\r\n\t\t\text.drawElementsInstancedANGLE( primitive, length, indexBuffer.buffer.gl_type, offset, batch_length );\r\n\t\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null );\r\n\t\t}\r\n\t\telse\r\n\t\t\text.drawArraysInstancedANGLE( primitive, offset, length, batch_length);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tif(indexBuffer)\r\n\t\t{\r\n\t\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer);\r\n\t\t\tgl.drawElementsInstanced( primitive, length, indexBuffer.buffer.gl_type, offset, batch_length );\r\n\t\t\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null );\r\n\t\t}\r\n\t\telse\r\n\t\t\tgl.drawArraysInstanced( primitive, offset, length, batch_length);\r\n\t}\r\n\r\n\t//disable instancing buffers\r\n\tfor(var i = 0; i < index; ++i)\r\n\t{\r\n\t\tvar info = Shader._instancing_arrays[ i ];\r\n\t\tvar uniformLocation = this.attributes[ info.uniform ];\r\n\t\tvar element_size = info.element_size;\r\n\t\tif( element_size == 16) //mat4\r\n\t\t{\r\n\t\t\tfor(var k = 0; k < 4; ++k)\r\n\t\t\t{\r\n\t\t\t\tgl.disableVertexAttribArray( uniformLocation+k );\r\n\t\t\t\tif( ext ) //webgl 1\r\n\t\t\t\t\text.vertexAttribDivisorANGLE( uniformLocation+k, 0 );\r\n\t\t\t\telse\r\n\t\t\t\t\tgl.vertexAttribDivisor( uniformLocation+k, 0 ); \r\n\t\t\t}\r\n\t\t}\r\n\t\telse //others\r\n\t\t{\r\n\t\t\tgl.enableVertexAttribArray( uniformLocation );\r\n\t\t\tif( ext ) //webgl 1\r\n\t\t\t\text.vertexAttribDivisorANGLE( uniformLocation, 0 );\r\n\t\t\telse\r\n\t\t\t\tgl.vertexAttribDivisor( uniformLocation, 0 );\r\n\t\t}\r\n\t}\r\n\r\n\treturn this;\r\n}\r\n\r\n\r\n\r\n/**\r\n* Given a source code with the directive #import it expands it inserting the code using Shader.files to fetch for import files.\r\n* Warning: Imports are evaluated only the first inclusion, the rest are ignored to avoid double inclusion of functions\r\n*          Also, imports cannot have other imports inside.\r\n* @method Shader.expandImports\r\n* @param {String} code the source code\r\n* @param {Object} files [Optional] object with files to import from (otherwise Shader.files is used)\r\n* @return {String} the code with the lines #import removed and replaced by the code\r\n*/\r\nShader.expandImports = function(code, files)\r\n{\r\n\tfiles = files || Shader.files;\r\n\r\n\tvar already_imported = {}; //avoid to import two times the same code\r\n\tif( !files )\r\n\t\tthrow(\"Shader.files not initialized, assign files there\");\r\n\r\n\tvar replace_import = function(v)\r\n\t{\r\n\t\tvar token = v.split(\"\\\"\");\r\n\t\tvar id = token[1];\r\n\t\tif( already_imported[id] )\r\n\t\t\treturn \"//already imported: \" + id + \"\\n\";\r\n\t\tvar file = files[id];\r\n\t\talready_imported[ id ] = true;\r\n\t\tif(file)\r\n\t\t\treturn file + \"\\n\";\r\n\t\treturn \"//import code not found: \" + id + \"\\n\";\r\n\t}\r\n\r\n\t//return code.replace(/#import\\s+\\\"(\\w+)\\\"\\s*\\n/g, replace_import );\r\n\treturn code.replace(/#import\\s+\\\"([a-zA-Z0-9_\\.]+)\\\"\\s*\\n/g, replace_import );\r\n}\r\n\r\nShader.dumpErrorToConsole = function(err, vscode, fscode)\r\n{\r\n\tconsole.error(err);\r\n\tvar msg = err.msg;\r\n\tvar code = null;\r\n\tif(err.indexOf(\"Fragment\") != -1)\r\n\t\tcode = fscode;\r\n\telse\r\n\t\tcode = vscode;\r\n\r\n\tvar lines = code.split(\"\\n\");\r\n\tfor(var i in lines)\r\n\t\tlines[i] = i + \"| \" + lines[i];\r\n\r\n\tconsole.groupCollapsed(\"Shader code\");\r\n\tconsole.log( lines.join(\"\\n\") );\r\n\tconsole.groupEnd();\r\n}\r\n\r\nShader.convertTo100 = function(code,type)\r\n{\r\n\t//in VERTEX\r\n\t\t//change in for attribute\r\n\t\t//change out for varying\r\n\t\t//add #extension GL_OES_standard_derivatives\r\n\t//in FRAGMENT\r\n\t\t//change in for varying \r\n\t\t//remove out vec4 _gl_FragColor\r\n\t\t//rename _gl_FragColor for gl_FragColor\r\n\t//in both\r\n\t\t//change #version 300 es for #version 100\r\n\t\t//replace 'texture(' for 'texture2D('\r\n}\r\n\r\n\r\nShader.convertTo300 = function(code,type)\r\n{\r\n\t//in VERTEX\r\n\t\t//change attribute for in\r\n\t\t//change varying for out\r\n\t\t//remove #extension GL_OES_standard_derivatives\r\n\t//in FRAGMENT\r\n\t\t//change varying for in\r\n\t\t//rename gl_FragColor for _gl_FragColor\r\n\t\t//rename gl_FragData[0] for _gl_FragColor\r\n\t\t//add out vec4 _gl_FragColor\r\n\t//in both\r\n\t\t//replace texture2D for texture\r\n}\r\n\r\n//helps to check if a variable value is valid to an specific uniform in a shader\r\nShader.validateValue = function( value, uniform_info )\r\n{\r\n\tif(value === null || value === undefined)\r\n\t\treturn false;\r\n\r\n\tswitch (uniform_info.type)\r\n\t{\r\n\t\t//used to validate shaders\r\n\t\tcase GL.INT: \r\n\t\tcase GL.FLOAT: \r\n\t\tcase GL.SAMPLER_2D: \r\n\t\tcase GL.SAMPLER_CUBE: \r\n\t\t\treturn isNumber(value);\r\n\t\tcase GL.INT_VEC2: \r\n\t\tcase GL.FLOAT_VEC2:\r\n\t\t\treturn value.length === 2;\r\n\t\tcase GL.INT_VEC3: \r\n\t\tcase GL.FLOAT_VEC3:\r\n\t\t\treturn value.length === 3;\r\n\t\tcase GL.INT_VEC4: \r\n\t\tcase GL.FLOAT_VEC4:\r\n\t\tcase GL.FLOAT_MAT2:\r\n\t\t\t return value.length === 4;\r\n\t\tcase GL.FLOAT_MAT3:\r\n\t\t\t return value.length === 8;\r\n\t\tcase GL.FLOAT_MAT4:\r\n\t\t\t return value.length === 16;\r\n\t}\r\n\treturn true;\r\n}\r\n\r\n//**************** SHADERS ***********************************\r\n\r\nShader.DEFAULT_VERTEX_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tattribute vec3 a_vertex;\\n\\\r\n\t\t\tattribute vec3 a_normal;\\n\\\r\n\t\t\tattribute vec2 a_coord;\\n\\\r\n\t\t\tvarying vec3 v_position;\\n\\\r\n\t\t\tvarying vec3 v_normal;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform mat4 u_model;\\n\\\r\n\t\t\tuniform mat4 u_mvp;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tv_position = (u_model * vec4(a_vertex,1.0)).xyz;\\n\\\r\n\t\t\t\tv_normal = (u_model * vec4(a_normal,0.0)).xyz;\\n\\\r\n\t\t\t\tv_coord = a_coord;\\n\\\r\n\t\t\t\tgl_Position = u_mvp * vec4(a_vertex,1.0);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\nShader.SCREEN_VERTEX_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tattribute vec3 a_vertex;\\n\\\r\n\t\t\tattribute vec2 a_coord;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tvoid main() { \\n\\\r\n\t\t\t\tv_coord = a_coord; \\n\\\r\n\t\t\t\tgl_Position = vec4(a_coord * 2.0 - 1.0, 0.0, 1.0); \\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\nShader.SCREEN_FRAGMENT_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tgl_FragColor = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\n//used in createFX\r\nShader.SCREEN_FRAGMENT_FX = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\t#ifdef FX_UNIFORMS\\n\\\r\n\t\t\t\tFX_UNIFORMS\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec2 uv = v_coord;\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, uv);\\n\\\r\n\t\t\t\t#ifdef FX_CODE\\n\\\r\n\t\t\t\t\tFX_CODE ;\\n\\\r\n\t\t\t\t#endif\\n\\\r\n\t\t\t\tgl_FragColor = color;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\nShader.SCREEN_COLORED_FRAGMENT_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec4 u_color;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tgl_FragColor = u_color * texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\nShader.BLEND_FRAGMENT_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform sampler2D u_texture2;\\n\\\r\n\t\t\tuniform float u_factor;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tgl_FragColor = mix( texture2D(u_texture, v_coord), texture2D(u_texture2, v_coord), u_factor);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\n//used to paint quads\r\nShader.QUAD_VERTEX_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tattribute vec3 a_vertex;\\n\\\r\n\t\t\tattribute vec2 a_coord;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform vec2 u_position;\\n\\\r\n\t\t\tuniform vec2 u_size;\\n\\\r\n\t\t\tuniform vec2 u_viewport;\\n\\\r\n\t\t\tuniform mat3 u_transform;\\n\\\r\n\t\t\tvoid main() { \\n\\\r\n\t\t\t\tvec3 pos = vec3(u_position + vec2(a_coord.x,1.0 - a_coord.y)  * u_size, 1.0);\\n\\\r\n\t\t\t\tv_coord = a_coord; \\n\\\r\n\t\t\t\tpos = u_transform * pos;\\n\\\r\n\t\t\t\tpos.z = 0.0;\\n\\\r\n\t\t\t\t//normalize\\n\\\r\n\t\t\t\tpos.x = (2.0 * pos.x / u_viewport.x) - 1.0;\\n\\\r\n\t\t\t\tpos.y = -((2.0 * pos.y / u_viewport.y) - 1.0);\\n\\\r\n\t\t\t\tgl_Position = vec4(pos, 1.0); \\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\nShader.QUAD_FRAGMENT_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec4 u_color;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tgl_FragColor = u_color * texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\n//used to render partially a texture\r\nShader.QUAD2_FRAGMENT_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec4 u_color;\\n\\\r\n\t\t\tuniform vec4 u_texture_area;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t    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\\\r\n\t\t\t\tgl_FragColor = u_color * texture2D(u_texture, uv);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\nShader.PRIMITIVE2D_VERTEX_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tattribute vec3 a_vertex;\\n\\\r\n\t\t\tuniform vec2 u_viewport;\\n\\\r\n\t\t\tuniform mat3 u_transform;\\n\\\r\n\t\t\tvoid main() { \\n\\\r\n\t\t\t\tvec3 pos = a_vertex;\\n\\\r\n\t\t\t\tpos = u_transform * pos;\\n\\\r\n\t\t\t\tpos.z = 0.0;\\n\\\r\n\t\t\t\t//normalize\\n\\\r\n\t\t\t\tpos.x = (2.0 * pos.x / u_viewport.x) - 1.0;\\n\\\r\n\t\t\t\tpos.y = -((2.0 * pos.y / u_viewport.y) - 1.0);\\n\\\r\n\t\t\t\tgl_Position = vec4(pos, 1.0); \\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\nShader.FLAT_VERTEX_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tattribute vec3 a_vertex;\\n\\\r\n\t\t\tuniform mat4 u_mvp;\\n\\\r\n\t\t\tvoid main() { \\n\\\r\n\t\t\t\tgl_Position = u_mvp * vec4(a_vertex,1.0); \\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\nShader.FLAT_FRAGMENT_SHADER = \"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tuniform vec4 u_color;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tgl_FragColor = u_color;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\nShader.SCREEN_FLAT_FRAGMENT_SHADER = Shader.FLAT_FRAGMENT_SHADER; //legacy\r\n\r\n\r\n/**\r\n* 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\r\n* @method Shader.createFX\r\n* @param {string} code string containg code, like \"color = color * 2.0;\"\r\n* @param {string} [uniforms=null] string containg extra uniforms, like \"uniform vec3 u_pos;\"\r\n*/\r\nShader.createFX = function(code, uniforms, shader)\r\n{\r\n\t//remove comments\r\n\tcode = GL.Shader.removeComments( code, true ); //remove comments and breaklines to avoid problems with the macros\r\n\tvar macros = {\r\n\t\tFX_CODE: code,\r\n\t\tFX_UNIFORMS: uniforms || \"\"\r\n\t}\r\n\tif(!shader)\r\n\t\treturn new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, GL.Shader.SCREEN_FRAGMENT_FX, macros );\r\n\tshader.updateShader( GL.Shader.SCREEN_VERTEX_SHADER, GL.Shader.SCREEN_FRAGMENT_FX, macros );\r\n\treturn shader;\r\n}\r\n\r\n/**\r\n* Given a shader code with some vars inside (like {{varname}}) and an object with the variable values, it will replace them.\r\n* @method Shader.replaceCodeUsingContext\r\n* @param {string} code string containg code and vars in {{varname}} format\r\n* @param {object} context object containing all var values\r\n*/\r\nShader.replaceCodeUsingContext = function( code_template, context )\r\n{\r\n\treturn code_template.replace(/\\{\\{[a-zA-Z0-9_]*\\}\\}/g, function(v){\r\n\t\tv = v.replace( /[\\{\\}]/g, \"\" );\r\n\t\treturn context[v] || \"\";\r\n\t});\r\n}\r\n\r\nShader.removeComments = function(code, one_line)\r\n{\r\n\tif(!code)\r\n\t\treturn \"\";\r\n\r\n\tvar rx = /(\\/\\*([^*]|[\\r\\n]|(\\*+([^*\\/]|[\\r\\n])))*\\*+\\/)|(\\/\\/.*)/g;\r\n\tvar code = code.replace( rx ,\"\");\r\n\tvar lines = code.split(\"\\n\");\r\n\tvar result = [];\r\n\tfor(var i = 0; i < lines.length; ++i)\r\n\t{\r\n\t\tvar line = lines[i]; \r\n\t\tvar pos = line.indexOf(\"//\");\r\n\t\tif(pos != -1)\r\n\t\t\tline = lines[i].substr(0,pos);\r\n\t\tline = line.trim();\r\n\t\tif(line.length)\r\n\t\t\tresult.push(line);\r\n\t}\r\n\treturn result.join( one_line ? \"\" : \"\\n\" );\r\n}\r\n\r\n/**\r\n* Renders a fullscreen quad with this shader applied\r\n* @method toViewport\r\n* @param {object} uniforms\r\n*/\r\nShader.prototype.toViewport = function(uniforms)\r\n{\r\n\tvar mesh = GL.Mesh.getScreenQuad();\r\n\tif(uniforms)\r\n\t\tthis.uniforms(uniforms);\r\n\tthis.draw( mesh );\r\n}\r\n\r\n//Now some common shaders everybody needs\r\n\r\n/**\r\n* Returns a shader ready to render a textured quad in fullscreen, use with Mesh.getScreenQuad() mesh\r\n* shader params: sampler2D u_texture\r\n* @method Shader.getScreenShader\r\n*/\r\nShader.getScreenShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":screen\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\tshader = gl.shaders[\":screen\"] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, Shader.SCREEN_FRAGMENT_SHADER );\r\n\treturn shader.uniforms({u_texture:0}); //do it the first time so I dont have to do it every time\r\n}\r\n\r\n/**\r\n* Returns a shader ready to render a flat color quad in fullscreen, use with Mesh.getScreenQuad() mesh\r\n* shader params: vec4 u_color\r\n* @method Shader.getFlatScreenShader\r\n*/\r\nShader.getFlatScreenShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":flat_screen\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\tshader = gl.shaders[\":flat_screen\"] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, Shader.FLAT_FRAGMENT_SHADER );\r\n\treturn shader.uniforms({u_color:[1,1,1,1]}); //do it the first time so I dont have to do it every time\r\n}\r\n\r\n/**\r\n* Returns a shader ready to render a colored textured quad in fullscreen, use with Mesh.getScreenQuad() mesh\r\n* shader params vec4 u_color and sampler2D u_texture\r\n* @method Shader.getColoredScreenShader\r\n*/\r\nShader.getColoredScreenShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":colored_screen\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\tshader = gl.shaders[\":colored_screen\"] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, Shader.SCREEN_COLORED_FRAGMENT_SHADER );\r\n\treturn 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\r\n}\r\n\r\n/**\r\n* Returns a shader ready to render a quad with transform, use with Mesh.getScreenQuad() mesh\r\n* shader must have: u_position, u_size, u_viewport, u_transform (mat3)\r\n* @method Shader.getQuadShader\r\n*/\r\nShader.getQuadShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":quad\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\treturn gl.shaders[\":quad\"] = new GL.Shader( Shader.QUAD_VERTEX_SHADER, Shader.QUAD_FRAGMENT_SHADER );\r\n}\r\n\r\n/**\r\n* Returns a shader ready to render part of a texture into the viewport\r\n* shader must have: u_position, u_size, u_viewport, u_transform, u_texture_area (vec4)\r\n* @method Shader.getPartialQuadShader\r\n*/\r\nShader.getPartialQuadShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":quad2\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\treturn gl.shaders[\":quad2\"] = new GL.Shader( Shader.QUAD_VERTEX_SHADER, Shader.QUAD2_FRAGMENT_SHADER );\r\n}\r\n\r\n/**\r\n* Returns a shader that blends two textures\r\n* shader must have: u_factor, u_texture, u_texture2\r\n* @method Shader.getBlendShader\r\n*/\r\nShader.getBlendShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":blend\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\treturn gl.shaders[\":blend\"] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, Shader.BLEND_FRAGMENT_SHADER );\r\n}\r\n\r\n/**\r\n* Returns a shader used to apply gaussian blur to one texture in one axis (you should use it twice to get a gaussian blur)\r\n* shader params are: vec2 u_offset, float u_intensity\r\n* @method Shader.getBlurShader\r\n*/\r\nShader.getBlurShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":blur\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\r\n\tvar shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,\"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec2 u_offset;\\n\\\r\n\t\t\tuniform float u_intensity;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t   vec4 sum = vec4(0.0);\\n\\\r\n\t\t\t   sum += texture2D(u_texture, v_coord + u_offset * -4.0) * 0.05/0.98;\\n\\\r\n\t\t\t   sum += texture2D(u_texture, v_coord + u_offset * -3.0) * 0.09/0.98;\\n\\\r\n\t\t\t   sum += texture2D(u_texture, v_coord + u_offset * -2.0) * 0.12/0.98;\\n\\\r\n\t\t\t   sum += texture2D(u_texture, v_coord + u_offset * -1.0) * 0.15/0.98;\\n\\\r\n\t\t\t   sum += texture2D(u_texture, v_coord) * 0.16/0.98;\\n\\\r\n\t\t\t   sum += texture2D(u_texture, v_coord + u_offset * 4.0) * 0.05/0.98;\\n\\\r\n\t\t\t   sum += texture2D(u_texture, v_coord + u_offset * 3.0) * 0.09/0.98;\\n\\\r\n\t\t\t   sum += texture2D(u_texture, v_coord + u_offset * 2.0) * 0.12/0.98;\\n\\\r\n\t\t\t   sum += texture2D(u_texture, v_coord + u_offset * 1.0) * 0.15/0.98;\\n\\\r\n\t\t\t   gl_FragColor = u_intensity * sum;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\");\r\n\treturn gl.shaders[\":blur\"] = shader;\r\n}\r\n\r\n//shader to copy a depth texture into another one\r\nShader.getCopyDepthShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":copy_depth\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\r\n\tvar shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,\"\\n\\\r\n\t\t\t#extension GL_EXT_frag_depth : enable\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t   gl_FragDepthEXT = texture2D( u_texture, v_coord ).x;\\n\\\r\n\t\t\t   gl_FragColor = vec4(1.0);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\");\r\n\treturn gl.shaders[\":copy_depth\"] = shader;\r\n}\r\n\r\nShader.getCubemapShowShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":show_cubemap\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\r\n\tvar shader = new GL.Shader( Shader.DEFAULT_VERTEX_SHADER,\"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec3 v_normal;\\n\\\r\n\t\t\tuniform samplerCube u_texture;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t   gl_FragColor = textureCube( u_texture, v_normal );\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\");\r\n\tshader.uniforms({u_texture:0});\r\n\treturn gl.shaders[\":show_cubemap\"] = shader;\r\n}\r\n\r\n//shader to copy a cubemap into another \r\nShader.getPolarToCubemapShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":polar_to_cubemap\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\r\n\tvar shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,\"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform mat3 u_rotation;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec2 uv = vec2( v_coord.x, 1.0 - v_coord.y );\\n\\\r\n\t\t\t\tvec3 dir = normalize( vec3( uv - vec2(0.5), 0.5 ));\\n\\\r\n\t\t\t\tdir = u_rotation * dir;\\n\\\r\n\t\t\t\tfloat u = atan(dir.x,dir.z) / 6.28318531;\\n\\\r\n\t\t\t\tfloat v = (asin(dir.y) / 1.57079633) * 0.5 + 0.5;\\n\\\r\n\t\t\t\tu = mod(u,1.0);\\n\\\r\n\t\t\t\tv = mod(v,1.0);\\n\\\r\n\t\t\t   gl_FragColor = texture2D( u_texture, vec2(u,v) );\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\");\r\n\treturn gl.shaders[\":polar_to_cubemap\"] = shader;\r\n}\r\n\r\n\r\n//shader to copy a cubemap into another \r\nShader.getCubemapCopyShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":copy_cubemap\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\r\n\tvar shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,\"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform samplerCube u_texture;\\n\\\r\n\t\t\tuniform mat3 u_rotation;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec2 uv = vec2( v_coord.x, 1.0 - v_coord.y );\\n\\\r\n\t\t\t\tvec3 dir = vec3( uv - vec2(0.5), 0.5 );\\n\\\r\n\t\t\t\tdir = u_rotation * dir;\\n\\\r\n\t\t\t   gl_FragColor = textureCube( u_texture, dir );\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\");\r\n\treturn gl.shaders[\":copy_cubemap\"] = shader;\r\n}\r\n\r\n//shader to blur a cubemap\r\nShader.getCubemapBlurShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":blur_cubemap\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\r\n\tvar shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,\"\\n\\\r\n\t\t\t#ifndef NUM_SAMPLES\\n\\\r\n\t\t\t\t#define NUM_SAMPLES 4\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform samplerCube u_texture;\\n\\\r\n\t\t\tuniform mat3 u_rotation;\\n\\\r\n\t\t\tuniform vec2 u_offset;\\n\\\r\n\t\t\tuniform float u_intensity;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 sum = vec4(0.0);\\n\\\r\n\t\t\t\tvec2 uv = vec2( v_coord.x, 1.0 - v_coord.y ) - vec2(0.5);\\n\\\r\n\t\t\t\tvec3 dir = vec3(0.0);\\n\\\r\n\t\t\t\tvec4 color = vec4(0.0);\\n\\\r\n\t\t\t\tfor( int x = -2; x <= 2; x++ )\\n\\\r\n\t\t\t\t{\\n\\\r\n\t\t\t\t\tfor( int y = -2; y <= 2; y++ )\\n\\\r\n\t\t\t\t\t{\\n\\\r\n\t\t\t\t\t\tdir.xy = uv + vec2( u_offset.x * float(x), u_offset.y * float(y)) * 0.5;\\n\\\r\n\t\t\t\t\t\tdir.z = 0.5;\\n\\\r\n\t\t\t\t\t\tdir = u_rotation * dir;\\n\\\r\n\t\t\t\t\t\tcolor = textureCube( u_texture, dir );\\n\\\r\n\t\t\t\t\t\tcolor.xyz = color.xyz * color.xyz;/*linearize*/\\n\\\r\n\t\t\t\t\t\tsum += color;\\n\\\r\n\t\t\t\t\t}\\n\\\r\n\t\t\t\t}\\n\\\r\n\t\t\t\tsum /= 25.0;\\n\\\r\n\t\t\t   gl_FragColor = vec4( sqrt( sum.xyz ), sum.w ) ;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\");\r\n\treturn gl.shaders[\":blur_cubemap\"] = shader;\r\n}\r\n\r\n//shader to do FXAA (antialiasing)\r\nShader.FXAA_FUNC = \"\\n\\\r\n\tuniform vec2 u_viewportSize;\\n\\\r\n\tuniform vec2 u_iViewportSize;\\n\\\r\n\t#define FXAA_REDUCE_MIN   (1.0/ 128.0)\\n\\\r\n\t#define FXAA_REDUCE_MUL   (1.0 / 8.0)\\n\\\r\n\t#define FXAA_SPAN_MAX     8.0\\n\\\r\n\t\\n\\\r\n\t/* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\\n\\\r\n\tvec4 applyFXAA(sampler2D tex, vec2 fragCoord)\\n\\\r\n\t{\\n\\\r\n\t\tvec4 color = vec4(0.0);\\n\\\r\n\t\t/*vec2 u_iViewportSize = vec2(1.0 / u_viewportSize.x, 1.0 / u_viewportSize.y);*/\\n\\\r\n\t\tvec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * u_iViewportSize).xyz;\\n\\\r\n\t\tvec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * u_iViewportSize).xyz;\\n\\\r\n\t\tvec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * u_iViewportSize).xyz;\\n\\\r\n\t\tvec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * u_iViewportSize).xyz;\\n\\\r\n\t\tvec3 rgbM  = texture2D(tex, fragCoord  * u_iViewportSize).xyz;\\n\\\r\n\t\tvec3 luma = vec3(0.299, 0.587, 0.114);\\n\\\r\n\t\tfloat lumaNW = dot(rgbNW, luma);\\n\\\r\n\t\tfloat lumaNE = dot(rgbNE, luma);\\n\\\r\n\t\tfloat lumaSW = dot(rgbSW, luma);\\n\\\r\n\t\tfloat lumaSE = dot(rgbSE, luma);\\n\\\r\n\t\tfloat lumaM  = dot(rgbM,  luma);\\n\\\r\n\t\tfloat lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\\n\\\r\n\t\tfloat lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\\n\\\r\n\t\t\\n\\\r\n\t\tvec2 dir;\\n\\\r\n\t\tdir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\\n\\\r\n\t\tdir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));\\n\\\r\n\t\t\\n\\\r\n\t\tfloat dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\\n\\\r\n\t\t\\n\\\r\n\t\tfloat rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\\n\\\r\n\t\tdir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * u_iViewportSize;\\n\\\r\n\t\t\\n\\\r\n\t\tvec3 rgbA = 0.5 * (texture2D(tex, fragCoord * u_iViewportSize + dir * (1.0 / 3.0 - 0.5)).xyz + \\n\\\r\n\t\t\ttexture2D(tex, fragCoord * u_iViewportSize + dir * (2.0 / 3.0 - 0.5)).xyz);\\n\\\r\n\t\tvec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * u_iViewportSize + dir * -0.5).xyz + \\n\\\r\n\t\t\ttexture2D(tex, fragCoord * u_iViewportSize + dir * 0.5).xyz);\\n\\\r\n\t\t\\n\\\r\n\t\treturn vec4(rgbA,1.0);\\n\\\r\n\t\tfloat lumaB = dot(rgbB, luma);\\n\\\r\n\t\tif ((lumaB < lumaMin) || (lumaB > lumaMax))\\n\\\r\n\t\t\tcolor = vec4(rgbA, 1.0);\\n\\\r\n\t\telse\\n\\\r\n\t\t\tcolor = vec4(rgbB, 1.0);\\n\\\r\n\t\treturn color;\\n\\\r\n\t}\\n\\\r\n\";\r\n\r\n/**\r\n* Returns a shader to apply FXAA antialiasing\r\n* params are vec2 u_viewportSize, vec2 u_iViewportSize or you can call shader.setup()\r\n* @method Shader.getFXAAShader\r\n*/\r\nShader.getFXAAShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":fxaa\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\r\n\tvar shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER,\"\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\t\" + Shader.FXAA_FUNC + \"\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t   gl_FragColor = applyFXAA( u_texture, v_coord * u_viewportSize) ;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\");\r\n\r\n\tvar viewport = vec2.fromValues( gl.viewport_data[2], gl.viewport_data[3] );\r\n\tvar iviewport = vec2.fromValues( 1/gl.viewport_data[2], 1/gl.viewport_data[3] );\r\n\r\n\tshader.setup = function() {\r\n\t\tviewport[0] = gl.viewport_data[2];\r\n\t\tviewport[1] = gl.viewport_data[3];\r\n\t\tiviewport[0] = 1/gl.viewport_data[2];\r\n\t\tiviewport[1] = 1/gl.viewport_data[3];\r\n\t\tthis.uniforms({ u_viewportSize: viewport, u_iViewportSize: iviewport });\t\r\n\t}\r\n\treturn gl.shaders[\":fxaa\"] = shader;\r\n}\r\n\r\n/**\r\n* Returns a flat shader (useful to render lines)\r\n* @method Shader.getFlatShader\r\n*/\r\nShader.getFlatShader = function(gl)\r\n{\r\n\tgl = gl || global.gl;\r\n\tvar shader = gl.shaders[\":flat\"];\r\n\tif(shader)\r\n\t\treturn shader;\r\n\r\n\tvar shader = new GL.Shader( Shader.FLAT_VERTEX_SHADER,Shader.FLAT_FRAGMENT_SHADER);\r\n\tshader.uniforms({u_color:[1,1,1,1]});\r\n\treturn gl.shaders[\":flat\"] = shader;\r\n}\n\r\n/**\r\n* 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.\r\n* @class GL\r\n*/\r\n\r\n/**\r\n* creates a new WebGL context (it can create the canvas or use an existing one)\r\n* @method create\r\n* @param {Object} options supported are: \r\n* - width\r\n* - height\r\n* - canvas\r\n* - container (string or element)\r\n* @return {WebGLRenderingContext} webgl context with all the extra functions (check gl in the doc for more info)\r\n*/\r\nGL.create = function(options) {\r\n\toptions = options || {};\r\n\tvar canvas = null;\r\n\tif(options.canvas)\r\n\t{\r\n\t\tif(typeof(options.canvas) == \"string\")\r\n\t\t{\r\n\t\t\tcanvas = document.getElementById( options.canvas );\r\n\t\t\tif(!canvas) throw(\"Canvas element not found: \" + options.canvas );\r\n\t\t}\r\n\t\telse \r\n\t\t\tcanvas = options.canvas;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar root = null;\r\n\t\tif(options.container)\r\n\t\t\troot = options.container.constructor === String ? document.querySelector( options.container ) : options.container;\r\n\t\tif(root && !options.width)\r\n\t\t{\r\n\t\t\tvar rect = root.getBoundingClientRect();\r\n\t\t\toptions.width = rect.width;\r\n\t\t\toptions.height = rect.height;\r\n\t\t}\r\n\r\n\t\tcanvas = createCanvas(  options.width || 800, options.height || 600 );\r\n\t\tif(root)\r\n\t\t\troot.appendChild(canvas);\r\n\t}\r\n\r\n\tif (!('alpha' in options)) options.alpha = false;\r\n\r\n\r\n\t/**\r\n\t* the webgl context returned by GL.create, its a WebGLRenderingContext with some extra methods added\r\n\t* @class gl\r\n\t*/\r\n\tvar gl = null;\r\n\r\n\tvar seq = null;\r\n\tif(options.version == 2)\t\r\n\t\tseq = ['webgl2','experimental-webgl2'];\r\n\telse if(options.version == 1 || options.version === undefined) //default\r\n\t\tseq = ['webgl','experimental-webgl'];\r\n\telse if(options.version === 0) //latest\r\n\t\tseq = ['webgl2','experimental-webgl2','webgl','experimental-webgl'];\r\n\r\n\tif(!seq)\r\n\t\tthrow 'Incorrect WebGL version, must be 1 or 2';\r\n\r\n\tvar context_options = {\r\n\t\talpha: options.alpha === undefined ? true : options.alpha,\r\n\t\tdepth: options.depth === undefined ? true : options.depth,\r\n\t\tstencil: options.stencil === undefined ? true : options.stencil,\r\n\t\tantialias: options.antialias === undefined ? true : options.antialias,\r\n\t\tpremultipliedAlpha: options.premultipliedAlpha === undefined ? true : options.premultipliedAlpha,\r\n\t\tpreserveDrawingBuffer: options.preserveDrawingBuffer === undefined ? true : options.preserveDrawingBuffer\r\n\t};\r\n\r\n\tfor(var i = 0; i < seq.length; ++i)\r\n\t{\r\n\t\ttry { gl = canvas.getContext( seq[i], context_options ); } catch (e) {}\r\n\t\tif(gl)\r\n\t\t\tbreak;\r\n\t}\r\n\r\n\tif (!gl)\r\n\t{\r\n\t\tif( canvas.getContext( \"webgl\" ) )\r\n\t\t\tthrow 'WebGL supported but not with those parameters';\r\n\t\tthrow 'WebGL not supported';\r\n\t}\r\n\r\n\t//context globals\r\n\tgl.webgl_version = gl.constructor.name === \"WebGL2RenderingContext\" ? 2 : 1;\r\n\tglobal.gl = gl;\r\n\tcanvas.is_webgl = true;\r\n\tcanvas.gl = gl;\r\n\tgl.context_id = this.last_context_id++;\r\n\r\n\t//get all supported extensions\r\n\tvar supported_extensions = gl.getSupportedExtensions();\r\n\tgl.extensions = {};\r\n\tfor(var i in supported_extensions)\r\n\t\tgl.extensions[ supported_extensions[i] ] = gl.getExtension( supported_extensions[i] );\r\n\tgl.derivatives_supported = gl.extensions['OES_standard_derivatives'] != null || gl.webgl_version > 1;\r\n\r\n\t/*\r\n\tgl.extensions[\"OES_standard_derivatives\"] = gl.derivatives_supported = gl.getExtension('OES_standard_derivatives') || false;\r\n\tgl.extensions[\"WEBGL_depth_texture\"] = gl.getExtension(\"WEBGL_depth_texture\") || gl.getExtension(\"WEBKIT_WEBGL_depth_texture\") || gl.getExtension(\"MOZ_WEBGL_depth_texture\");\r\n\tgl.extensions[\"OES_element_index_uint\"] = gl.getExtension(\"OES_element_index_uint\");\r\n\tgl.extensions[\"WEBGL_draw_buffers\"] = gl.getExtension(\"WEBGL_draw_buffers\");\r\n\tgl.extensions[\"EXT_shader_texture_lod\"] = gl.getExtension(\"EXT_shader_texture_lod\");\r\n\tgl.extensions[\"EXT_sRGB\"] = gl.getExtension(\"EXT_sRGB\");\r\n\tgl.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\");\r\n\tgl.extensions[\"EXT_frag_depth\"] = gl.getExtension(\"EXT_frag_depth\") || gl.getExtension(\"WEBKIT_EXT_frag_depth\") || gl.getExtension(\"MOZ_EXT_frag_depth\");\r\n\tgl.extensions[\"WEBGL_lose_context\"] = gl.getExtension(\"WEBGL_lose_context\") || gl.getExtension(\"WEBKIT_WEBGL_lose_context\") || gl.getExtension(\"MOZ_WEBGL_lose_context\");\r\n\tgl.extensions[\"ANGLE_instanced_arrays\"] = gl.getExtension(\"ANGLE_instanced_arrays\");\r\n\tgl.extensions[\"disjoint_timer_query\"] = gl.getExtension(\"EXT_disjoint_timer_query\");\r\n\r\n\t//for float textures\r\n\tgl.extensions[\"OES_texture_float_linear\"] = gl.getExtension(\"OES_texture_float_linear\");\r\n\tif(gl.extensions[\"OES_texture_float_linear\"])\r\n\t\tgl.extensions[\"OES_texture_float\"] = gl.getExtension(\"OES_texture_float\");\r\n\tgl.extensions[\"EXT_color_buffer_float\"] = gl.getExtension(\"EXT_color_buffer_float\");\r\n\r\n\t//for half float textures in webgl 1 require extension\r\n\tgl.extensions[\"OES_texture_half_float_linear\"] = gl.getExtension(\"OES_texture_half_float_linear\");\r\n\tif(gl.extensions[\"OES_texture_half_float_linear\"])\r\n\t\tgl.extensions[\"OES_texture_half_float\"] = gl.getExtension(\"OES_texture_half_float\");\r\n\t*/\r\n\r\n\tif( gl.webgl_version == 1 )\r\n\t\tgl.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\r\n\telse\r\n\t\tgl.HIGH_PRECISION_FORMAT = GL.HALF_FLOAT_OES;\r\n\r\n\tgl.max_texture_units = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);\r\n\r\n\t//viewport hack to retrieve it without using getParameter (which is slow and generates garbage)\r\n\tif(!gl._viewport_func)\r\n\t{\r\n\t\tgl._viewport_func = gl.viewport;\r\n\t\tgl.viewport_data = new Float32Array([0,0,gl.canvas.width,gl.canvas.height]); //32000 max viewport, I guess its fine\r\n\t\tgl.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); }\r\n\t\tgl.getViewport = function(v) { \r\n\t\t\tif(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; }\r\n\t\t\treturn new Float32Array( gl.viewport_data );\r\n\t\t};\r\n\t\tgl.setViewport = function( v, flip_y ) {\r\n\t\t\tgl.viewport_data.set(v);\r\n\t\t\tif(flip_y)\r\n\t\t\t\tgl.viewport_data[1] = this.drawingBufferHeight-v[1]-v[3];\r\n\t\t\tthis._viewport_func(v[0],gl.viewport_data[1],v[2],v[3]);\r\n\t\t};\r\n\t}\r\n\telse\r\n\t\tconsole.warn(\"Creating LiteGL context over the same canvas twice\");\r\n\r\n\t//reverse names helper (assuming no names repeated)\r\n\tif(!GL.reverse)\r\n\t{\r\n\t\tGL.reverse = {}; \r\n\t\tfor(var i in gl)\r\n\t\t\tif( gl[i] && gl[i].constructor === Number )\r\n\t\t\t\tGL.reverse[ gl[i] ] = i;\r\n\t}\r\n\t\r\n\t//just some checks\r\n\tif(typeof(glMatrix) == \"undefined\")\r\n\t\tthrow(\"glMatrix not found, LiteGL requires glMatrix to be included\");\r\n\r\n\tvar last_click_time = 0;\r\n\r\n\t//some global containers, use them to reuse assets\r\n\tgl.shaders = {};\r\n\tgl.textures = {};\r\n\tgl.meshes = {};\r\n\r\n\t/**\r\n\t* sets this context as the current global gl context (in case you have more than one)\r\n\t* @method makeCurrent\r\n\t*/\r\n\tgl.makeCurrent = function()\r\n\t{\r\n\t\tglobal.gl = this;\r\n\t}\r\n\r\n\t/**\r\n\t* executes callback inside this webgl context\r\n\t* @method execute\r\n\t* @param {Function} callback\r\n\t*/\r\n\tgl.execute = function(callback)\r\n\t{\r\n\t\tvar old_gl = global.gl;\r\n\t\tglobal.gl = this;\r\n\t\tcallback();\r\n\t\tglobal.gl = old_gl;\r\n\t}\r\n\r\n\r\n\t/**\r\n\t* Launch animation loop (calls gl.onupdate and gl.ondraw every frame)\r\n\t* example: gl.ondraw = function(){ ... }   or  gl.onupdate = function(dt) { ... }\r\n\t* @method animate\r\n\t*/\r\n\tgl.animate = function(v) {\r\n\t\tif(v === false)\r\n\t\t{\r\n\t\t\tglobal.cancelAnimationFrame( this._requestFrame_id );\r\n\t\t\tthis._requestFrame_id = null;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar post = global.requestAnimationFrame;\r\n\t\tvar time = getTime();\r\n\t\tvar context = this;\r\n\r\n\t\t//loop only if browser tab visible\r\n\t\tfunction loop() {\r\n\t\t\tif(gl.destroyed) //to stop rendering once it is destroyed\r\n\t\t\t\treturn;\r\n\r\n\t\t\tcontext._requestFrame_id = post(loop); //do it first, in case it crashes\r\n\r\n\t\t\tvar now = getTime();\r\n\t\t\tvar dt = (now - time) * 0.001;\r\n\t\t\tif(context.mouse)\r\n\t\t\t\tcontext.mouse.last_buttons = context.mouse.buttons;\r\n\t\t\tif (context.onupdate) \r\n\t\t\t\tcontext.onupdate(dt);\r\n\t\t\tLEvent.trigger( context, \"update\", dt);\r\n\t\t\tif (context.ondraw)\r\n\t\t\t{\r\n\t\t\t\t//make sure the ondraw is called using this gl context (in case there is more than one)\r\n\t\t\t\tvar old_gl = global.gl;\r\n\t\t\t\tglobal.gl = context;\r\n\t\t\t\t//call ondraw\r\n\t\t\t\tcontext.ondraw();\r\n\t\t\t\tLEvent.trigger(context,\"draw\");\r\n\t\t\t\t//restore old context\r\n\t\t\t\tglobal.gl = old_gl;\r\n\t\t\t}\r\n\t\t\ttime = now;\r\n\t\t}\r\n\t\tthis._requestFrame_id = post(loop); //launch main loop\r\n\t}\t\r\n\r\n\t//store binded to be able to remove them if destroyed\r\n\t/*\r\n\tvar _binded_events = [];\r\n\tfunction addEvent(object, type, callback)\r\n\t{\r\n\t\t_binded_events.push(object,type,callback);\r\n\t}\r\n\t*/\r\n\r\n\t/**\r\n\t* Destroy this WebGL context (removes also the Canvas from the DOM)\r\n\t* @method destroy\r\n\t*/\r\n\tgl.destroy = function() {\r\n\t\t//unbind global events\r\n\t\tif(onkey_handler)\r\n\t\t{\r\n\t\t\tdocument.removeEventListener(\"keydown\", onkey_handler );\r\n\t\t\tdocument.removeEventListener(\"keyup\", onkey_handler );\r\n\t\t}\r\n\r\n\t\tif(this.canvas.parentNode)\r\n\t\t\tthis.canvas.parentNode.removeChild(this.canvas);\r\n\t\tthis.destroyed = true;\r\n\t\tif(global.gl == this)\r\n\t\t\tglobal.gl = null;\r\n\t}\r\n\r\n\tvar mouse = gl.mouse = {\r\n\t\tbuttons: 0, //this should always be up-to-date with mouse state\r\n\t\tlast_buttons: 0, //button state in the previous frame\r\n\t\tleft_button: false,\r\n\t\tmiddle_button: false,\r\n\t\tright_button: false,\r\n\t\tposition: new Float32Array(2),\r\n\t\tx:0, //in canvas coordinates\r\n\t\ty:0,\r\n\t\tdeltax: 0,\r\n\t\tdeltay: 0,\r\n\t\tclientx:0, //in client coordinates\r\n\t\tclienty:0,\r\n\t\tisInsideRect: function(x,y,w,h, flip_y )\r\n\t\t{\r\n\t\t\tvar mouse_y = this.y;\r\n\t\t\tif(flip_y)\r\n\t\t\t\tmouse_y = gl.canvas.height - mouse_y;\r\n\t\t\tif( this.x > x && this.x < x + w &&\r\n\t\t\t\tmouse_y > y && mouse_y < y + h)\r\n\t\t\t\treturn true;\r\n\t\t\treturn false;\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t* returns true if button num is pressed (where num could be GL.LEFT_MOUSE_BUTTON, GL.RIGHT_MOUSE_BUTTON, GL.MIDDLE_MOUSE_BUTTON\r\n\t\t* @method captureMouse\r\n\t\t* @param {boolean} capture_wheel capture also the mouse wheel\r\n\t\t*/\r\n\t\tisButtonPressed: function(num)\r\n\t\t{\r\n\t\t\tif(num == GL.LEFT_MOUSE_BUTTON)\r\n\t\t\t\treturn this.buttons & GL.LEFT_MOUSE_BUTTON_MASK;\r\n\t\t\tif(num == GL.MIDDLE_MOUSE_BUTTON)\r\n\t\t\t\treturn this.buttons & GL.MIDDLE_MOUSE_BUTTON_MASK;\r\n\t\t\tif(num == GL.RIGHT_MOUSE_BUTTON)\r\n\t\t\t\treturn this.buttons & GL.RIGHT_MOUSE_BUTTON_MASK;\r\n\t\t},\r\n\r\n\t\twasButtonPressed: function(num)\r\n\t\t{\r\n\t\t\tvar mask = 0;\r\n\t\t\tif(num == GL.LEFT_MOUSE_BUTTON)\r\n\t\t\t\tmask = GL.LEFT_MOUSE_BUTTON_MASK;\r\n\t\t\telse if(num == GL.MIDDLE_MOUSE_BUTTON)\r\n\t\t\t\tmask = GL.MIDDLE_MOUSE_BUTTON_MASK;\r\n\t\t\telse if(num == GL.RIGHT_MOUSE_BUTTON)\r\n\t\t\t\tmask = GL.RIGHT_MOUSE_BUTTON_MASK;\r\n\t\t\treturn (this.buttons & mask) && !(this.last_buttons & mask);\r\n\t\t}\r\n\t};\r\n\r\n\t/**\r\n\t* Tells the system to capture mouse events on the canvas. \r\n\t* This will trigger onmousedown, onmousemove, onmouseup, onmousewheel callbacks assigned in the gl context\r\n\t* example: gl.onmousedown = function(e){ ... }\r\n\t* The event is a regular MouseEvent with some extra parameters\r\n\t* @method captureMouse\r\n\t* @param {boolean} capture_wheel capture also the mouse wheel\r\n\t*/\r\n\tgl.captureMouse = function(capture_wheel, translate_touchs ) {\r\n\r\n\t\tcanvas.addEventListener(\"mousedown\", onmouse);\r\n\t\tcanvas.addEventListener(\"mousemove\", onmouse);\r\n\t\tcanvas.addEventListener(\"dragstart\", onmouse);\r\n\t\t//canvas.addEventListener(\"mouseup\", onmouse); ??\r\n\t\tif(capture_wheel)\r\n\t\t{\r\n\t\t\tcanvas.addEventListener(\"mousewheel\", onmouse, false);\r\n\t\t\tcanvas.addEventListener(\"wheel\", onmouse, false);\r\n\t\t\t//canvas.addEventListener(\"DOMMouseScroll\", onmouse, false); //deprecated or non-standard\r\n\t\t}\r\n\t\t//prevent right click context menu\r\n\t\tcanvas.addEventListener(\"contextmenu\", function(e) { e.preventDefault(); return false; });\r\n\r\n\t\tif( translate_touchs )\r\n\t\t\tthis.captureTouch( true );\r\n\t}\r\n\r\n\tfunction onmouse(e) {\r\n\r\n\t\tif(gl.ignore_events)\r\n\t\t\treturn;\r\n\t\t//console.log(e.type); //debug\r\n\t\tvar old_mouse_mask = gl.mouse.buttons;\r\n\t\tGL.augmentEvent(e, canvas);\r\n\t\te.eventType = e.eventType || e.type; //type cannot be overwritten, so I make a clone to allow me to overwrite\r\n\t\tvar now = getTime();\r\n\r\n\t\t//gl.mouse info\r\n\t\tmouse.dragging = e.dragging;\r\n\t\tmouse.position[0] = e.canvasx;\r\n\t\tmouse.position[1] = e.canvasy;\r\n\t\tmouse.x = e.canvasx;\r\n\t\tmouse.y = e.canvasy;\r\n\t\tmouse.mousex = e.mousex;\r\n\t\tmouse.mousey = e.mousey;\r\n\t\tmouse.canvasx = e.canvasx;\r\n\t\tmouse.canvasy = e.canvasy;\r\n\t\tmouse.clientx = e.mousex;\r\n\t\tmouse.clienty = e.mousey;\r\n\t\tmouse.buttons = e.buttons;\r\n\t\tmouse.left_button = !!(mouse.buttons & GL.LEFT_MOUSE_BUTTON_MASK);\r\n\t\tmouse.middle_button = !!(mouse.buttons & GL.MIDDLE_MOUSE_BUTTON_MASK);\r\n\t\tmouse.right_button = !!(mouse.buttons & GL.RIGHT_MOUSE_BUTTON_MASK);\r\n\r\n\t\tif(e.eventType == \"mousedown\")\r\n\t\t{\r\n\t\t\tif(old_mouse_mask == 0) //no mouse button was pressed till now\r\n\t\t\t{\r\n\t\t\t\tcanvas.removeEventListener(\"mousemove\", onmouse);\r\n\t\t\t\tvar doc = canvas.ownerDocument;\r\n\t\t\t\tdoc.addEventListener(\"mousemove\", onmouse);\r\n\t\t\t\tdoc.addEventListener(\"mouseup\", onmouse);\r\n\t\t\t}\r\n\t\t\tlast_click_time = now;\r\n\r\n\t\t\tif(gl.onmousedown)\r\n\t\t\t\tgl.onmousedown(e);\r\n\t\t\tLEvent.trigger(gl,\"mousedown\");\r\n\t\t}\r\n\t\telse if(e.eventType == \"mousemove\")\r\n\t\t{ \r\n\t\t\tif(gl.onmousemove)\r\n\t\t\t\tgl.onmousemove(e); \r\n\t\t\tLEvent.trigger(gl,\"mousemove\",e);\r\n\t\t} \r\n\t\telse if(e.eventType == \"mouseup\")\r\n\t\t{\r\n\t\t\t//console.log(\"mouseup\");\r\n\t\t\tif(gl.mouse.buttons == 0) //no more buttons pressed\r\n\t\t\t{\r\n\t\t\t\tcanvas.addEventListener(\"mousemove\", onmouse);\r\n\t\t\t\tvar doc = canvas.ownerDocument;\r\n\t\t\t\tdoc.removeEventListener(\"mousemove\", onmouse);\r\n\t\t\t\tdoc.removeEventListener(\"mouseup\", onmouse);\r\n\t\t\t}\r\n\t\t\te.click_time = now - last_click_time;\r\n\t\t\t//last_click_time = now; //commented to avoid reseting click time when unclicking two mouse buttons\r\n\r\n\t\t\tif(gl.onmouseup)\r\n\t\t\t\tgl.onmouseup(e);\r\n\t\t\tLEvent.trigger(gl,\"mouseup\",e);\r\n\t\t}\r\n\t\telse if((e.eventType == \"mousewheel\" || e.eventType == \"wheel\" || e.eventType == \"DOMMouseScroll\"))\r\n\t\t{ \r\n\t\t\te.eventType = \"mousewheel\";\r\n\t\t\tif(e.type == \"wheel\")\r\n\t\t\t\te.wheel = -e.deltaY; //in firefox deltaY is 1 while in Chrome is 120\r\n\t\t\telse\r\n\t\t\t\te.wheel = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60);\r\n\r\n\t\t\t//from stack overflow\r\n\t\t\t//firefox doesnt have wheelDelta\r\n\t\t\te.delta = e.wheelDelta !== undefined ? (e.wheelDelta/40) : (e.deltaY ? -e.deltaY/3 : 0);\r\n\t\t\t//console.log(e.delta);\r\n\t\t\tif(gl.onmousewheel)\r\n\t\t\t\tgl.onmousewheel(e);\r\n\r\n\t\t\tLEvent.trigger(gl, \"mousewheel\", e);\r\n\t\t}\r\n\t\telse if(e.eventType == \"dragstart\")\r\n\t\t{\r\n\t\t\tif(gl.ondragstart)\r\n\t\t\t\tgl.ondragstart(e);\r\n\t\t\tLEvent.trigger(gl, \"dragstart\", e);\r\n\t\t}\r\n\r\n\t\tif(gl.onmouse)\r\n\t\t\tgl.onmouse(e);\r\n\r\n\t\tif(!e.skip_preventDefault)\r\n\t\t{\r\n\t\t\tif(e.eventType != \"mousemove\")\r\n\t\t\t\te.stopPropagation();\r\n\t\t\te.preventDefault();\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\tvar translate_touches = false;\r\n\r\n\tgl.captureTouch = function( translate_to_mouse_events )\r\n\t{\r\n\t\ttranslate_touches = translate_to_mouse_events;\r\n\r\n\t\tcanvas.addEventListener(\"touchstart\", ontouch, true);\r\n\t\tcanvas.addEventListener(\"touchmove\", ontouch, true);\r\n\t\tcanvas.addEventListener(\"touchend\", ontouch, true);\r\n\t\tcanvas.addEventListener(\"touchcancel\", ontouch, true);   \r\n\r\n\t\tcanvas.addEventListener('gesturestart', ongesture );\r\n\t\tcanvas.addEventListener('gesturechange', ongesture );\r\n\t\tcanvas.addEventListener('gestureend', ongesture );\r\n\t}\r\n\r\n\t//translates touch events in mouseevents\r\n\tfunction ontouch( e )\r\n\t{\r\n\t\tvar touches = e.changedTouches,\r\n\t\t\tfirst = touches[0],\r\n\t\t\ttype = \"\";\r\n\r\n\t\tif( gl.ontouch && gl.ontouch(e) === true )\r\n\t\t\treturn;\r\n\r\n\t\tif( LEvent.trigger( gl, e.type, e ) === true )\r\n\t\t\treturn;\r\n\r\n\t\tif(!translate_touches)\r\n\t\t\treturn;\r\n\r\n\t\t//ignore secondary touches\r\n        if(e.touches.length && e.changedTouches[0].identifier !== e.touches[0].identifier)\r\n        \treturn;\r\n        \t\r\n\t\tif(touches > 1)\r\n\t\t\treturn;\r\n\r\n\t\t switch(e.type)\r\n\t\t{\r\n\t\t\tcase \"touchstart\": type = \"mousedown\"; break;\r\n\t\t\tcase \"touchmove\":  type = \"mousemove\"; break;        \r\n\t\t\tcase \"touchend\":   type = \"mouseup\"; break;\r\n\t\t\tdefault: return;\r\n\t\t}\r\n\r\n\t\tvar simulatedEvent = document.createEvent(\"MouseEvent\");\r\n\t\tsimulatedEvent.initMouseEvent(type, true, true, window, 1,\r\n\t\t\t\t\t\t\t\t  first.screenX, first.screenY,\r\n\t\t\t\t\t\t\t\t  first.clientX, first.clientY, false,\r\n\t\t\t\t\t\t\t\t  false, false, false, 0/*left*/, null);\r\n\t\tsimulatedEvent.originalEvent = simulatedEvent;\r\n\t\tsimulatedEvent.is_touch = true;\r\n\t\tfirst.target.dispatchEvent( simulatedEvent );\r\n\t\te.preventDefault();\r\n\t}\r\n\r\n\tfunction ongesture(e)\r\n\t{\r\n\t\te.eventType = e.type;\r\n\r\n\t\tif(gl.ongesture && gl.ongesture(e) === false )\r\n\t\t\treturn;\r\n\r\n\t\tif( LEvent.trigger( gl, e.type, e ) === false )\r\n\t\t\treturn;\r\n\r\n\t\te.preventDefault();\r\n\t}\r\n\r\n\tvar keys = gl.keys = {};\r\n\r\n\t/**\r\n\t* Tells the system to capture key events on the canvas. This will trigger onkey\r\n\t* @method captureKeys\r\n\t* @param {boolean} prevent_default prevent default behaviour (like scroll on the web, etc)\r\n\t* @param {boolean} only_canvas only caches keyboard events if they happen when the canvas is in focus\r\n\t*/\r\n\tvar onkey_handler = null;\r\n\tgl.captureKeys = function( prevent_default, only_canvas ) {\r\n\t\tif(onkey_handler) \r\n\t\t\treturn;\r\n\t\tgl.keys = {};\r\n\r\n\t\tvar target = only_canvas ? gl.canvas : document;\r\n\r\n\t\tdocument.addEventListener(\"keydown\", inner );\r\n\t\tdocument.addEventListener(\"keyup\", inner );\r\n\t\tfunction inner(e) { onkey(e, prevent_default); }\r\n\t\tonkey_handler = inner;\r\n\t}\r\n\r\n\r\n\r\n\tfunction onkey(e, prevent_default)\r\n\t{\r\n\t\te.eventType = e.type; //type cannot be overwritten, so I make a clone to allow me to overwrite\r\n\r\n\t\tvar target_element = e.target.nodeName.toLowerCase();\r\n\t\tif(target_element === \"input\" || target_element === \"textarea\" || target_element === \"select\")\r\n\t\t\treturn;\r\n\r\n\t\te.character = String.fromCharCode(e.keyCode).toLowerCase();\r\n\t\tvar prev_state = false;\r\n\t\tvar key = GL.mapKeyCode(e.keyCode);\r\n\t\tif(!key) //this key doesnt look like an special key\r\n\t\t\tkey = e.character;\r\n\r\n\t\t//regular key\r\n\t\tif (!e.altKey && !e.ctrlKey && !e.metaKey) {\r\n\t\t\tif (key) \r\n\t\t\t\tgl.keys[key] = e.type == \"keydown\";\r\n\t\t\tprev_state = gl.keys[e.keyCode];\r\n\t\t\tgl.keys[e.keyCode] = e.type == \"keydown\";\r\n\t\t}\r\n\r\n\t\t//avoid repetition if key stays pressed\r\n\t\tif(prev_state != gl.keys[e.keyCode])\r\n\t\t{\r\n\t\t\tif(e.type == \"keydown\" && gl.onkeydown) \r\n\t\t\t\tgl.onkeydown(e);\r\n\t\t\telse if(e.type == \"keyup\" && gl.onkeyup) \r\n\t\t\t\tgl.onkeyup(e);\r\n\t\t\tLEvent.trigger(gl, e.type, e);\r\n\t\t}\r\n\r\n\t\tif(gl.onkey)\r\n\t\t\tgl.onkey(e);\r\n\r\n\t\tif(prevent_default && (e.isChar || GL.blockable_keys[e.keyIdentifier || e.key ]) )\r\n\t\t\te.preventDefault();\r\n\t}\r\n\r\n\t//gamepads\r\n\tgl.gamepads = null;\r\n\t/*\r\n\tfunction onButton(e, pressed)\r\n\t{\r\n\t\tconsole.log(e);\r\n\t\tif(pressed && gl.onbuttondown)\r\n\t\t\tgl.onbuttondown(e);\r\n\t\telse if(!pressed && gl.onbuttonup)\r\n\t\t\tgl.onbuttonup(e);\r\n\t\tif(gl.onbutton)\r\n\t\t\tgl.onbutton(e);\r\n\t\tLEvent.trigger(gl, pressed ? \"buttondown\" : \"buttonup\", e );\r\n\t}\r\n\tfunction onGamepad(e)\r\n\t{\r\n\t\tconsole.log(e);\r\n\t\tif(gl.ongamepad) \r\n\t\t\tgl.ongamepad(e);\r\n\t}\r\n\t*/\r\n\r\n\t/**\r\n\t* Tells the system to capture gamepad events on the canvas. \r\n\t* @method captureGamepads\r\n\t*/\r\n\tgl.captureGamepads = function()\r\n\t{\r\n\t\tvar getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads; \r\n\t\tif(!getGamepads) return;\r\n\t\tthis.gamepads = getGamepads.call(navigator);\r\n\r\n\t\t//only in firefox, so I cannot rely on this\r\n\t\t/*\r\n\t\twindow.addEventListener(\"gamepadButtonDown\", function(e) { onButton(e, true); }, false);\r\n\t\twindow.addEventListener(\"MozGamepadButtonDown\", function(e) { onButton(e, true); }, false);\r\n\t\twindow.addEventListener(\"WebkitGamepadButtonDown\", function(e) { onButton(e, true); }, false);\r\n\t\twindow.addEventListener(\"gamepadButtonUp\", function(e) { onButton(e, false); }, false);\r\n\t\twindow.addEventListener(\"MozGamepadButtonUp\", function(e) { onButton(e, false); }, false);\r\n\t\twindow.addEventListener(\"WebkitGamepadButtonUp\", function(e) { onButton(e, false); }, false);\r\n\t\twindow.addEventListener(\"gamepadconnected\", onGamepad, false);\r\n\t\twindow.addEventListener(\"gamepaddisconnected\", onGamepad, false);\r\n\t\t*/\r\n\r\n\t}\r\n\r\n\t/**\r\n\t* returns the detected gamepads on the system\r\n\t* @method getGamepads\r\n\t* @param {bool} skip_mapping if set to true it returns the basic gamepad, otherwise it returns a class with mapping info to XBOX controller\r\n\t*/\r\n\tgl.getGamepads = function(skip_mapping)\r\n\t{\r\n\t\t//gamepads\r\n\t\tvar getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads; \r\n\t\tif(!getGamepads)\r\n\t\t\treturn;\r\n\t\tvar gamepads = getGamepads.call(navigator);\r\n\t\tif(!this.gamepads)\r\n\t\t\tthis.gamepads = [];\r\n\r\n\t\t//iterate to generate events\r\n\t\tfor(var i = 0; i < 4; i++)\r\n\t\t{\r\n\t\t\tvar gamepad = gamepads[i]; //current state\r\n\r\n\t\t\tif(gamepad && !skip_mapping)\r\n\t\t\t\taddGamepadXBOXmapping(gamepad);\r\n\r\n\t\t\t//launch connected gamepads events\r\n\t\t\tif(gamepad && !gamepad.prev_buttons)\r\n\t\t\t{\r\n\t\t\t\tgamepad.prev_buttons = new Uint8Array(32);\r\n\t\t\t\tvar event = new CustomEvent(\"gamepadconnected\");\r\n\t\t\t\tevent.eventType = event.type;\r\n\t\t\t\tevent.gamepad = gamepad;\r\n\t\t\t\tif(this.ongamepadconnected)\r\n\t\t\t\t\tthis.ongamepadconnected(event);\r\n\t\t\t\tLEvent.trigger(gl,\"gamepadconnected\",event);\r\n\t\t\t}\r\n\t\t\t/*\r\n\t\t\telse if(old_gamepad && !gamepad)\r\n\t\t\t{\r\n\t\t\t\tvar event = new CustomEvent(\"gamepaddisconnected\");\r\n\t\t\t\tevent.eventType = event.type;\r\n\t\t\t\tevent.gamepad = old_gamepad;\r\n\t\t\t\tif(this.ongamepaddisconnected)\r\n\t\t\t\t\tthis.ongamepaddisconnected(event);\r\n\t\t\t\tLEvent.trigger(gl,\"gamepaddisconnected\",event);\r\n\t\t\t}\r\n\t\t\t*/\r\n\r\n\t\t\t//seek buttons changes to trigger events\r\n\t\t\tif(gamepad)\r\n\t\t\t{\r\n\t\t\t\tfor(var j = 0; j < gamepad.buttons.length; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar button = gamepad.buttons[j];\r\n\t\t\t\t\tbutton.was_pressed = false;\r\n\t\t\t\t\tif( button.pressed && !gamepad.prev_buttons[j] )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbutton.was_pressed = true;\r\n\t\t\t\t\t\tvar event = new CustomEvent(\"gamepadButtonDown\");\r\n\t\t\t\t\t\tevent.eventType = event.type;\r\n\t\t\t\t\t\tevent.button = button;\r\n\t\t\t\t\t\tevent.which = j;\r\n\t\t\t\t\t\tevent.gamepad = gamepad;\r\n\t\t\t\t\t\tif(gl.onbuttondown)\r\n\t\t\t\t\t\t\tgl.onbuttondown(event);\r\n\t\t\t\t\t\tLEvent.trigger(gl,\"buttondown\",event);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if( !button.pressed && gamepad.prev_buttons[j] )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar event = new CustomEvent(\"gamepadButtonUp\");\r\n\t\t\t\t\t\tevent.eventType = event.type;\r\n\t\t\t\t\t\tevent.button = button;\r\n\t\t\t\t\t\tevent.which = j;\r\n\t\t\t\t\t\tevent.gamepad = gamepad;\r\n\t\t\t\t\t\tif(gl.onbuttondown)\r\n\t\t\t\t\t\t\tgl.onbuttondown(event);\r\n\t\t\t\t\t\tLEvent.trigger(gl,\"buttonup\",event);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tgamepad.prev_buttons[j] = button.pressed ? 1 : 0;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis.gamepads = gamepads;\r\n\t\treturn gamepads;\r\n\t}\r\n\r\n\tfunction addGamepadXBOXmapping(gamepad)\r\n\t{\r\n\t\t//xbox controller mapping\r\n\t\tvar xbox = gamepad.xbox || { axes:[], buttons:{}, hat: \"\"};\r\n\t\txbox.axes[\"lx\"] = gamepad.axes[0];\r\n\t\txbox.axes[\"ly\"] = gamepad.axes[1];\r\n\t\txbox.axes[\"rx\"] = gamepad.axes[2];\r\n\t\txbox.axes[\"ry\"] = gamepad.axes[3];\r\n\t\txbox.axes[\"triggers\"] = gamepad.axes[4];\r\n\r\n\t\tfor(var i = 0; i < gamepad.buttons.length; i++)\r\n\t\t{\r\n\t\t\tswitch(i) //I use a switch to ensure that a player with another gamepad could play\r\n\t\t\t{\r\n\t\t\t\tcase 0: xbox.buttons[\"a\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 1: xbox.buttons[\"b\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 2: xbox.buttons[\"x\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 3: xbox.buttons[\"y\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 4: xbox.buttons[\"lb\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 5: xbox.buttons[\"rb\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 6: xbox.buttons[\"lt\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 7: xbox.buttons[\"rt\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 8: xbox.buttons[\"back\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 9: xbox.buttons[\"start\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 10: xbox.buttons[\"ls\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 11: xbox.buttons[\"rs\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tcase 12: if( gamepad.buttons[i].pressed) xbox.hat += \"up\"; break;\r\n\t\t\t\tcase 13: if( gamepad.buttons[i].pressed) xbox.hat += \"down\"; break;\r\n\t\t\t\tcase 14: if( gamepad.buttons[i].pressed) xbox.hat += \"left\"; break;\r\n\t\t\t\tcase 15: if( gamepad.buttons[i].pressed) xbox.hat += \"right\"; break;\r\n\t\t\t\tcase 16: xbox.buttons[\"home\"] = gamepad.buttons[i].pressed; break;\r\n\t\t\t\tdefault:\r\n\t\t\t}\r\n\t\t}\r\n\t\tgamepad.xbox = xbox;\r\n\t}\r\n\r\n\t/**\r\n\t* launches de canvas in fullscreen mode\r\n\t* @method fullscreen\r\n\t*/\r\n\tgl.fullscreen = function()\r\n\t{\r\n\t\tvar canvas = this.canvas;\r\n\t\tif(canvas.requestFullScreen)\r\n\t\t\tcanvas.requestFullScreen();\r\n\t\telse if(canvas.webkitRequestFullScreen)\r\n\t\t\tcanvas.webkitRequestFullScreen();\r\n\t\telse if(canvas.mozRequestFullScreen)\r\n\t\t\tcanvas.mozRequestFullScreen();\r\n\t\telse\r\n\t\t\tconsole.error(\"Fullscreen not supported\");\r\n\t}\r\n\r\n\t/**\r\n\t* returns a canvas with a snapshot of an area\r\n\t* this is safer than using the canvas itself due to internals of webgl\r\n\t* @method snapshot\r\n\t* @param {Number} startx viewport x coordinate\r\n\t* @param {Number} starty viewport y coordinate from bottom\r\n\t* @param {Number} areax viewport area width\r\n\t* @param {Number} areay viewport area height\r\n\t* @return {Canvas} canvas\r\n\t*/\r\n\tgl.snapshot = function(startx, starty, areax, areay, skip_reverse)\r\n\t{\r\n\t\tvar canvas = createCanvas(areax,areay);\r\n\t\tvar ctx = canvas.getContext(\"2d\");\r\n\t\tvar pixels = ctx.getImageData(0,0,canvas.width,canvas.height);\r\n\r\n\t\tvar buffer = new Uint8Array(areax * areay * 4);\r\n\t\tgl.readPixels(startx, starty, canvas.width, canvas.height, gl.RGBA,gl.UNSIGNED_BYTE, buffer);\r\n\r\n\t\tpixels.data.set( buffer );\r\n\t\tctx.putImageData(pixels,0,0);\r\n\r\n\t\tif(skip_reverse)\r\n\t\t\treturn canvas;\r\n\r\n\t\t//flip image \r\n\t\tvar final_canvas = createCanvas(areax,areay);\r\n\t\tvar ctx = final_canvas.getContext(\"2d\");\r\n\t\tctx.translate(0,areay);\r\n\t\tctx.scale(1,-1);\r\n\t\tctx.drawImage(canvas,0,0);\r\n\r\n\t\treturn final_canvas;\r\n\t}\r\n\r\n\t//from https://webgl2fundamentals.org/webgl/lessons/webgl1-to-webgl2.html\r\n\tfunction getAndApplyExtension( gl, name ) {\r\n\t\tvar ext = gl.getExtension(name);\r\n\t\tif (!ext) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tvar suffix = name.split(\"_\")[0];\r\n\t\tvar prefix = suffix = '_';\r\n\t\tvar suffixRE = new RegExp(suffix + '$');\r\n\t\tvar prefixRE = new RegExp('^' + prefix);\r\n\t\tfor (var key in ext) {\r\n\t\t\tvar val = ext[key];\r\n\t\t\tif (typeof(val) === 'function') {\r\n\t\t\t\t// remove suffix (eg: bindVertexArrayOES -> bindVertexArray)\r\n\t\t\t\tvar unsuffixedKey = key.replace(suffixRE, '');\r\n\t\t\t\tif (key.substing)\r\n\t\t\t\t\tgl[unprefixedKey] = ext[key].bind(ext);\r\n\t\t\t} else {\r\n\t\t\t\tvar unprefixedKey = key.replace(prefixRE, '');\r\n\t\t\t\tgl[unprefixedKey] = ext[key];\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\r\n\t//mini textures manager\r\n\tvar loading_textures = {};\r\n\t/**\r\n\t* returns a texture and caches it inside gl.textures[]\r\n\t* @method loadTexture\r\n\t* @param {String} url\r\n\t* @param {Object} options (same options as when creating a texture)\r\n\t* @param {Function} callback function called once the texture is loaded\r\n\t* @return {Texture} texture\r\n\t*/\r\n\tgl.loadTexture = function(url, options, on_load)\r\n\t{\r\n\t\tif(this.textures[ url ])\r\n\t\t\treturn this.textures[url];\r\n\r\n\t\tif( loading_textures[url] )\r\n\t\t\treturn null;\r\n\r\n\t\tvar img = new Image();\r\n\t\timg.url = url;\r\n\t\timg.onload = function()\r\n\t\t{\r\n\t\t\tvar texture = GL.Texture.fromImage(this, options);\r\n\t\t\ttexture.img = this;\r\n\t\t\tgl.textures[this.url] = texture;\r\n\t\t\tdelete loading_textures[this.url];\r\n\t\t\tif(on_load)\r\n\t\t\t\ton_load(texture);\r\n\t\t} \r\n\t\timg.src = url;\r\n\t\tloading_textures[url] = true;\r\n\t\treturn null;\r\n\t}\r\n\r\n\t/**\r\n\t* draws a texture to the viewport\r\n\t* @method drawTexture\r\n\t* @param {Texture} texture\r\n\t* @param {number} x in viewport coordinates \r\n\t* @param {number} y in viewport coordinates \r\n\t* @param {number} w in viewport coordinates \r\n\t* @param {number} h in viewport coordinates \r\n\t* @param {number} tx texture x in texture coordinates\r\n\t* @param {number} ty texture y in texture coordinates\r\n\t* @param {number} tw texture width in texture coordinates\r\n\t* @param {number} th texture height in texture coordinates\r\n\t*/\r\n\tgl.drawTexture = (function() {\r\n\t\t//static variables: less garbage\r\n\t\tvar identity = mat3.create();\r\n\t\tvar pos = vec2.create();\r\n\t\tvar size = vec2.create();\r\n\t\tvar area = vec4.create();\r\n\t\tvar white = vec4.fromValues(1,1,1,1);\r\n\t\tvar viewport = vec2.create();\r\n\t\tvar _uniforms = {u_texture: 0, u_position: pos, u_color: white, u_size: size, u_texture_area: area, u_viewport: viewport, u_transform: identity };\r\n\r\n\t\treturn (function(texture, x,y, w,h, tx,ty, tw,th, shader, uniforms)\r\n\t\t{\r\n\t\t\tpos[0] = x;\tpos[1] = y;\r\n\t\t\tif(w === undefined)\r\n\t\t\t\tw = texture.width;\r\n\t\t\tif(h === undefined)\r\n\t\t\t\th = texture.height;\r\n\t\t\tsize[0] = w;\r\n\t\t\tsize[1] = h;\r\n\r\n\t\t\tif(tx === undefined) tx = 0;\r\n\t\t\tif(ty === undefined) ty = 0;\r\n\t\t\tif(tw === undefined) tw = texture.width;\r\n\t\t\tif(th === undefined) th = texture.height;\r\n\r\n\t\t\tarea[0] = tx / texture.width;\r\n\t\t\tarea[1] = ty / texture.height;\r\n\t\t\tarea[2] = (tx + tw) / texture.width;\r\n\t\t\tarea[3] = (ty + th) / texture.height;\r\n\r\n\t\t\tviewport[0] = this.viewport_data[2];\r\n\t\t\tviewport[1] = this.viewport_data[3];\r\n\r\n\t\t\tshader = shader || Shader.getPartialQuadShader(this);\r\n\t\t\tvar mesh = Mesh.getScreenQuad(this);\r\n\t\t\ttexture.bind(0);\r\n\t\t\tshader.uniforms( _uniforms );\r\n\t\t\tif( uniforms )\r\n\t\t\t\tshader.uniforms( uniforms );\r\n\t\t\tshader.draw( mesh, gl.TRIANGLES );\r\n\t\t});\r\n\t})();\r\n\r\n\tgl.canvas.addEventListener(\"webglcontextlost\", function(e) {\r\n\t\te.preventDefault();\r\n\t\tgl.context_lost = true;\r\n\t\tif(gl.onlosecontext)\r\n\t\t\tgl.onlosecontext(e);\r\n\t}, false);\r\n\r\n\t/**\r\n\t* use it to reset the the initial gl state\r\n\t* @method gl.reset\r\n\t*/\r\n\tgl.reset = function()\r\n\t{\r\n\t\t//viewport\r\n\t\tgl.viewport(0,0, this.canvas.width, this.canvas.height );\r\n\r\n\t\t//flags\r\n\t\tgl.disable( gl.BLEND );\r\n\t\tgl.disable( gl.CULL_FACE );\r\n\t\tgl.disable( gl.DEPTH_TEST );\r\n\t\tgl.frontFace( gl.CCW );\r\n\r\n\t\t//texture\r\n\t\tgl._current_texture_drawto = null;\r\n\t\tgl._current_fbo_color = null;\r\n\t\tgl._current_fbo_depth = null;\r\n\t}\r\n\r\n\tgl.dump = function()\r\n\t{\r\n\t\tconsole.log(\"userAgent: \", navigator.userAgent );\r\n\t\tconsole.log(\"Supported extensions:\");\r\n\t\tvar extensions = gl.getSupportedExtensions();\r\n\t\tconsole.log( extensions.join(\",\") );\r\n\t\tvar 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\" ];\r\n\t\tconsole.log(\"WebGL info:\");\r\n\t\tfor(var i in info)\r\n\t\t\tconsole.log(\" * \" + info[i] + \": \" + gl.getParameter( gl[info[i]] ));\r\n\t\tconsole.log(\"*************************************************\")\r\n\t}\r\n\r\n\t//Reset state\r\n\tgl.reset();\r\n\r\n\t//Return\r\n\treturn gl;\r\n}\r\n\r\nGL.mapKeyCode = function(code)\r\n{\r\n\tvar named = {\r\n\t\t8: 'BACKSPACE',\r\n\t\t9: 'TAB',\r\n\t\t13: 'ENTER',\r\n\t\t16: 'SHIFT',\r\n\t\t17: 'CTRL',\r\n\t\t27: 'ESCAPE',\r\n\t\t32: 'SPACE',\r\n\t\t37: 'LEFT',\r\n\t\t38: 'UP',\r\n\t\t39: 'RIGHT',\r\n\t\t40: 'DOWN'\r\n\t};\r\n\treturn named[code] || (code >= 65 && code <= 90 ? String.fromCharCode(code) : null);\r\n}\r\n\r\n//add useful info to the event\r\nGL.dragging = false;\r\nGL.last_pos = [0,0];\r\n\r\n//adds extra info to the MouseEvent (coordinates in canvas axis, deltas and button state)\r\nGL.augmentEvent = function(e, root_element)\r\n{\r\n\tvar offset_left = 0;\r\n\tvar offset_top = 0;\r\n\tvar b = null;\r\n\r\n\troot_element = root_element || e.target || gl.canvas;\r\n\tb = root_element.getBoundingClientRect();\r\n\t\t\r\n\te.mousex = e.clientX - b.left;\r\n\te.mousey = e.clientY - b.top;\r\n\te.canvasx = e.mousex;\r\n\te.canvasy = b.height - e.mousey;\r\n\te.deltax = 0;\r\n\te.deltay = 0;\r\n\t\r\n\tif(e.type == \"mousedown\")\r\n\t{\r\n\t\tthis.dragging = true;\r\n\t\t//gl.mouse.buttons |= (1 << e.which); //enable\r\n\t}\r\n\telse if (e.type == \"mousemove\")\r\n\t{\r\n\t}\r\n\telse if (e.type == \"mouseup\")\r\n\t{\r\n\t\t//gl.mouse.buttons = gl.mouse.buttons & ~(1 << e.which);\r\n\t\tif(e.buttons == 0)\r\n\t\t\tthis.dragging = false;\r\n\t}\r\n\r\n\tif( e.movementX !== undefined && !GL.isMobile() ) //pointer lock (mobile gives always zero)\r\n\t{\r\n\t\t//console.log( e.movementX )\r\n\t\te.deltax = e.movementX;\r\n\t\te.deltay = e.movementY;\r\n\t}\r\n\telse\r\n\t{\r\n\t\te.deltax = e.mousex - this.last_pos[0];\r\n\t\te.deltay = e.mousey - this.last_pos[1];\r\n\t}\r\n\tthis.last_pos[0] = e.mousex;\r\n\tthis.last_pos[1] = e.mousey;\r\n\r\n\t//insert info in event\r\n\te.dragging = this.dragging;\r\n\te.leftButton = !!(gl.mouse.buttons & GL.LEFT_MOUSE_BUTTON_MASK);\r\n\te.middleButton = !!(gl.mouse.buttons & GL.MIDDLE_MOUSE_BUTTON_MASK);\r\n\te.rightButton = !!(gl.mouse.buttons & GL.RIGHT_MOUSE_BUTTON_MASK);\r\n\t//shitty non-coherent API, e.buttons use 1:left,2:right,4:middle) but e.button uses (0:left,1:middle,2:right)\r\n\te.buttons_mask = 0;\r\n\tif( e.leftButton ) e.buttons_mask = 1;\r\n\tif( e.middleButton ) e.buttons_mask |= 2;\r\n\tif( e.rightButton ) e.buttons_mask |= 4;\r\n\te.isButtonPressed = function(num) { return this.buttons_mask & (1<<num); }\r\n}\r\n\r\n/**\r\n* Tells you if the app is running on a mobile device (iOS or Android)\r\n* @method isMobile\r\n* @return {boolean} true if is running on a iOS or Android device\r\n*/\r\nGL.isMobile = function()\r\n{\r\n\tif(this.mobile !== undefined)\r\n\t\treturn this.mobile;\r\n\r\n\tif(!global.navigator) //server side js?\r\n\t\treturn this.mobile = false;\r\n\r\n\tif( (navigator.userAgent.match(/iPhone/i)) || \r\n\t\t(navigator.userAgent.match(/iPod/i)) || \r\n\t\t(navigator.userAgent.match(/iPad/i)) || \r\n\t\t(navigator.userAgent.match(/Android/i))) {\r\n\t\treturn this.mobile = true;\r\n\t}\r\n\treturn this.mobile = false;\r\n}\n/**\r\n* @namespace \r\n*/\r\n\r\n/**\r\n* LEvent is a lightweight events library focused in low memory footprint and fast delivery.\r\n* It works by creating a property called \"__levents\" inside the object that has the bindings, and storing arrays with all the bindings.\r\n* @class LEvent\r\n* @constructor\r\n*/\r\n\r\nvar LEvent = global.LEvent = GL.LEvent = {\r\n\r\n\t/**\r\n\t* Binds an event to an instance\r\n\t* @method LEvent.bind\r\n\t* @param {Object} instance where to attach the event\r\n\t* @param {String} event_name string defining the event name\r\n\t* @param {function} callback function to call when the event is triggered\r\n\t* @param {Object} target_instance [Optional] instance to call the function (use this instead of .bind method to help removing events)\r\n\t**/\r\n\tbind: function( instance, event_type, callback, target_instance )\r\n\t{\r\n\t\tif(!instance) \r\n\t\t\tthrow(\"cannot bind event to null\");\r\n\t\tif(!callback) \r\n\t\t\tthrow(\"cannot bind to null callback\");\r\n\t\tif(instance.constructor === String ) \r\n\t\t\tthrow(\"cannot bind event to a string\");\r\n\r\n\t\tvar events = instance.__levents;\r\n\t\tif(!events)\r\n\t\t{\r\n\t\t\tObject.defineProperty( instance, \"__levents\", {value: {}, enumerable: false });\r\n\t\t\tevents = instance.__levents;\r\n\t\t}\r\n\r\n\t\tif( events.hasOwnProperty( event_type ) )\r\n\t\t\tevents[event_type].push([callback,target_instance]);\r\n\t\telse\r\n\t\t\tevents[event_type] = [[callback,target_instance]];\r\n\t\tif( instance.onLEventBinded )\r\n\t\t\tinstance.onLEventBinded( event_type, callback, target_instance );\r\n\t},\r\n\r\n\t/**\r\n\t* Unbinds an event from an instance\r\n\t* @method LEvent.unbind\r\n\t* @param {Object} instance where the event is binded\r\n\t* @param {String} event_name string defining the event name\r\n\t* @param {function} callback function that was binded\r\n\t* @param {Object} target_instance [Optional] target_instance that was binded\r\n\t**/\r\n\tunbind: function( instance, event_type, callback, target_instance )\r\n\t{\r\n\t\tif(!instance) \r\n\t\t\tthrow(\"cannot unbind event to null\");\r\n\t\tif(!callback) \r\n\t\t\tthrow(\"cannot unbind from null callback\");\r\n\t\tif(instance.constructor === String ) \r\n\t\t\tthrow(\"cannot bind event to a string\");\r\n\r\n\t\tvar events = instance.__levents;\r\n\t\tif(!events)\r\n\t\t\treturn;\r\n\r\n\t\tif(!events.hasOwnProperty( event_type ))\r\n\t\t\treturn;\r\n\r\n\t\tfor(var i = 0, l = events[event_type].length; i < l; ++i)\r\n\t\t{\r\n\t\t\tvar v = events[event_type][i];\r\n\t\t\tif(v[0] === callback && v[1] === target_instance)\r\n\t\t\t{\r\n\t\t\t\tevents[event_type].splice( i, 1 );\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (events[event_type].length == 0)\r\n\t\t\tdelete events[event_type];\r\n\r\n\t\tif( instance.onLEventUnbinded )\r\n\t\t\tinstance.onLEventUnbinded( event_type, callback, target_instance );\r\n\t},\r\n\r\n\t/**\r\n\t* Unbinds all events from an instance (or the ones that match certain target_instance)\r\n\t* @method LEvent.unbindAll\r\n\t* @param {Object} instance where the events are binded\r\n\t* @param {Object} target_instance [Optional] target_instance of the events to remove\r\n\t**/\r\n\tunbindAll: function( instance, target_instance, callback )\r\n\t{\r\n\t\tif(!instance) \r\n\t\t\tthrow(\"cannot unbind events in null\");\r\n\r\n\t\tvar events = instance.__levents;\r\n\t\tif(!events)\r\n\t\t\treturn;\r\n\r\n\t\tif( instance.onLEventUnbindAll )\r\n\t\t\tinstance.onLEventUnbindAll( target_instance, callback );\r\n\r\n\t\tif(!target_instance) //remove all\r\n\t\t{\r\n\t\t\tdelete instance.__levents;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t//remove only the target_instance\r\n\t\t//for every property in the instance\r\n\t\tfor(var i in events)\r\n\t\t{\r\n\t\t\tvar array = events[i];\r\n\t\t\tfor(var j = array.length - 1; j >= 0; --j) //iterate backwards to avoid problems after removing\r\n\t\t\t{\r\n\t\t\t\tif( array[j][1] != target_instance || (callback && callback !== array[j][0]) ) \r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\tarray.splice(j,1);//remove\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t* Unbinds all callbacks associated to one specific event from this instance\r\n\t* @method LEvent.unbindAll\r\n\t* @param {Object} instance where the events are binded\r\n\t* @param {String} event name of the event you want to remove all binds\r\n\t**/\r\n\tunbindAllEvent: function( instance, event_type )\r\n\t{\r\n\t\tif(!instance) \r\n\t\t\tthrow(\"cannot unbind events in null\");\r\n\r\n\t\tvar events = instance.__levents;\r\n\t\tif(!events)\r\n\t\t\treturn;\r\n\t\tdelete events[ event_type ];\r\n\t\tif( instance.onLEventUnbindAll )\r\n\t\t\tinstance.onLEventUnbindAll( event_type, target_instance, callback );\r\n\t\treturn;\r\n\t},\r\n\r\n\t/**\r\n\t* Tells if there is a binded callback that matches the criteria\r\n\t* @method LEvent.isBind\r\n\t* @param {Object} instance where the are the events binded\r\n\t* @param {String} event_name string defining the event name\r\n\t* @param {function} callback the callback\r\n\t* @param {Object} target_instance [Optional] instance binded to callback\r\n\t**/\r\n\tisBind: function( instance, event_type, callback, target_instance )\r\n\t{\r\n\t\tif(!instance)\r\n\t\t\tthrow(\"LEvent cannot have null as instance\");\r\n\r\n\t\tvar events = instance.__levents;\r\n\t\tif( !events )\r\n\t\t\treturn;\r\n\r\n\t\tif( !events.hasOwnProperty(event_type) ) \r\n\t\t\treturn false;\r\n\r\n\t\tfor(var i = 0, l = events[event_type].length; i < l; ++i)\r\n\t\t{\r\n\t\t\tvar v = events[event_type][i];\r\n\t\t\tif(v[0] === callback && v[1] === target_instance)\r\n\t\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t},\r\n\r\n\t/**\r\n\t* Tells if there is any callback binded to this event\r\n\t* @method LEvent.hasBind\r\n\t* @param {Object} instance where the are the events binded\r\n\t* @param {String} event_name string defining the event name\r\n\t* @return {boolean} true is there is at least one\r\n\t**/\r\n\thasBind: function( instance, event_type )\r\n\t{\r\n\t\tif(!instance)\r\n\t\t\tthrow(\"LEvent cannot have null as instance\");\r\n\t\tvar events = instance.__levents;\r\n\t\tif(!events || !events.hasOwnProperty( event_type ) || !events[event_type].length) \r\n\t\t\treturn false;\r\n\t\treturn true;\r\n\t},\r\n\r\n\t/**\r\n\t* Tells if there is any callback binded to this object pointing to a method in the target object\r\n\t* @method LEvent.hasBindTo\r\n\t* @param {Object} instance where there are the events binded\r\n\t* @param {Object} target instance to check to\r\n\t* @return {boolean} true is there is at least one\r\n\t**/\r\n\thasBindTo: function( instance, target )\r\n\t{\r\n\t\tif(!instance)\r\n\t\t\tthrow(\"LEvent cannot have null as instance\");\r\n\t\tvar events = instance.__levents;\r\n\r\n\t\t//no events binded\r\n\t\tif(!events) \r\n\t\t\treturn false;\r\n\r\n\t\tfor(var j in events)\r\n\t\t{\r\n\t\t\tvar binds = events[j];\r\n\t\t\tfor(var i = 0; i < binds.length; ++i)\r\n\t\t\t{\r\n\t\t\t\tif(binds[i][1] === target) //one found\r\n\t\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn false;\r\n\t},\r\n\r\n\t/**\r\n\t* Triggers and event in an instance\r\n\t* If the callback returns true then it will stop the propagation and return true\r\n\t* @method LEvent.trigger\r\n\t* @param {Object} instance that triggers the event\r\n\t* @param {String} event_name string defining the event name\r\n\t* @param {*} parameters that will be received by the binded function\r\n\t* @param {bool} reverse_order trigger in reverse order (binded last get called first)\r\n\t* @param {bool} expand_parameters parameters are passed not as one single parameter, but as many\r\n\t* return {bool} true if the event passed was blocked by any binded callback\r\n\t**/\r\n\ttrigger: function( instance, event_type, params, reverse_order, expand_parameters )\r\n\t{\r\n\t\tif(!instance) \r\n\t\t\tthrow(\"cannot trigger event from null\");\r\n\t\tif(instance.constructor === String ) \r\n\t\t\tthrow(\"cannot bind event to a string\");\r\n\r\n\t\tvar events = instance.__levents;\r\n\t\tif( !events || !events.hasOwnProperty(event_type) )\r\n\t\t\treturn false;\r\n\r\n\t\tvar inst = events[event_type];\r\n\t\tif( reverse_order )\r\n\t\t{\r\n\t\t\tfor(var i = inst.length - 1; i >= 0; --i)\r\n\t\t\t{\r\n\t\t\t\tvar v = inst[i];\r\n\t\t\t\tif(expand_parameters)\r\n\t\t\t\t{\r\n\t\t\t\t\tif( v && v[0].apply( v[1], params ) === true)// || event.stop)\r\n\t\t\t\t\t\treturn true; //stopPropagation\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tif( v && v[0].call( v[1], event_type, params) === true)// || event.stop)\r\n\t\t\t\t\t\treturn true; //stopPropagation\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tfor(var i = 0, l = inst.length; i < l; ++i)\r\n\t\t\t{\r\n\t\t\t\tvar v = inst[i];\r\n\t\t\t\tif( expand_parameters )\r\n\t\t\t\t{\r\n\t\t\t\t\tif( v && v[0].apply( v[1], params ) === true)// || event.stop)\r\n\t\t\t\t\t\treturn true; //stopPropagation\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tif( v && v[0].call(v[1], event_type, params) === true)// || event.stop)\r\n\t\t\t\t\t\treturn true; //stopPropagation\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn false;\r\n\t},\r\n\r\n\t/**\r\n\t* Triggers and event to every element in an array.\r\n\t* If the event returns true, it must be intercepted\r\n\t* @method LEvent.triggerArray\r\n\t* @param {Array} array contains all instances to triggers the event\r\n\t* @param {String} event_name string defining the event name\r\n\t* @param {*} parameters that will be received by the binded function\r\n\t* @param {bool} reverse_order trigger in reverse order (binded last get called first)\r\n\t* @param {bool} expand_parameters parameters are passed not as one single parameter, but as many\r\n\t* return {bool} false \r\n\t**/\r\n\ttriggerArray: function( instances, event_type, params, reverse_order, expand_parameters )\r\n\t{\r\n\t\tvar blocked = false;\r\n\t\tfor(var i = 0, l = instances.length; i < l; ++i)\r\n\t\t{\r\n\t\t\tvar instance = instances[i];\r\n\t\t\tif(!instance) \r\n\t\t\t\tthrow(\"cannot trigger event from null\");\r\n\t\t\tif(instance.constructor === String ) \r\n\t\t\t\tthrow(\"cannot bind event to a string\");\r\n\r\n\t\t\tvar events = instance.__levents;\r\n\t\t\tif( !events || !events.hasOwnProperty( event_type ) )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tif( reverse_order )\r\n\t\t\t{\r\n\t\t\t\tfor(var j = events[event_type].length - 1; j >= 0; --j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar v = events[event_type][j];\r\n\t\t\t\t\tif(expand_parameters)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif( v[0].apply(v[1], params ) === true)// || event.stop)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tblocked = true;\r\n\t\t\t\t\t\t\tbreak; //stopPropagation\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif( v[0].call(v[1], event_type, params) === true)// || event.stop)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tblocked = true;\r\n\t\t\t\t\t\t\tbreak; //stopPropagation\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tfor(var j = 0, ll = events[event_type].length; j < ll; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar v = events[event_type][j];\r\n\t\t\t\t\tif(expand_parameters)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif( v[0].apply(v[1], params ) === true)// || event.stop)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tblocked = true;\r\n\t\t\t\t\t\t\tbreak; //stopPropagation\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif( v[0].call(v[1], event_type, params) === true)// || event.stop)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tblocked = true;\r\n\t\t\t\t\t\t\tbreak; //stopPropagation\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn blocked;\r\n\t},\r\n\r\n\textendObject: function( object )\r\n\t{\r\n\t\tobject.bind = function( event_type, callback, instance ){\r\n\t\t\treturn LEvent.bind( this, event_type, callback, instance );\r\n\t\t};\r\n\r\n\t\tobject.trigger = function( event_type, params ){\r\n\t\t\treturn LEvent.trigger( this, event_type, params );\r\n\t\t};\r\n\r\n\t\tobject.unbind = function( event_type, callback, target_instance )\r\n\t\t{\r\n\t\t\treturn LEvent.unbind( this, event_type, callback, instance );\r\n\t\t};\r\n\r\n\t\tobject.unbindAll = function( target_instance, callback )\r\n\t\t{\r\n\t\t\treturn LEvent.unbindAll( this, target_instance, callback );\r\n\t\t};\r\n\t},\r\n\r\n\t/**\r\n\t* Adds the methods to bind, trigger and unbind to this class prototype\r\n\t* @method LEvent.extendClass\r\n\t* @param {Object} constructor\r\n\t**/\r\n\textendClass: function( constructor )\r\n\t{\r\n\t\tthis.extendObject( constructor.prototype );\r\n\t}\r\n};\n/* geometric utilities */\r\nglobal.CLIP_INSIDE = GL.CLIP_INSIDE = 0;\r\nglobal.CLIP_OUTSIDE = GL.CLIP_OUTSIDE = 1;\r\nglobal.CLIP_OVERLAP = GL.CLIP_OVERLAP = 2;\r\n\r\n/**\r\n* @namespace\r\n*/\r\n\r\n\r\n/**\r\n* Computational geometry algorithms, is a static class\r\n* @class geo\r\n*/\r\n\r\nglobal.geo = {\r\n\r\n\t/**\r\n\t* Returns a float4 containing the info about a plane with normal N and that passes through point P\r\n\t* @method createPlane\r\n\t* @param {vec3} P\r\n\t* @param {vec3} N\r\n\t* @return {vec4} plane values\r\n\t*/\r\n\tcreatePlane: function(P,N)\r\n\t{\r\n\t\treturn new Float32Array([N[0],N[1],N[2],-vec3.dot(P,N)]);\r\n\t},\r\n\r\n\t/**\r\n\t* Computes the distance between the point and the plane\r\n\t* @method distancePointToPlane\r\n\t* @param {vec3} point\r\n\t* @param {vec4} plane\r\n\t* @return {Number} distance\r\n\t*/\r\n\tdistancePointToPlane: function(point, plane)\r\n\t{\r\n\t\treturn (vec3.dot(point,plane) + plane[3])/Math.sqrt(plane[0]*plane[0] + plane[1]*plane[1] + plane[2]*plane[2]);\r\n\t},\r\n\r\n\t/**\r\n\t* Computes the square distance between the point and the plane\r\n\t* @method distance2PointToPlane\r\n\t* @param {vec3} point\r\n\t* @param {vec4} plane\r\n\t* @return {Number} distance*distance\r\n\t*/\r\n\tdistance2PointToPlane: function(point, plane)\r\n\t{\r\n\t\treturn (vec3.dot(point,plane) + plane[3])/(plane[0]*plane[0] + plane[1]*plane[1] + plane[2]*plane[2]);\r\n\t},\r\n\r\n\t/**\r\n\t* Projects a 3D point on a 3D line\r\n\t* @method projectPointOnLine\r\n\t* @param {vec3} P\r\n\t* @param {vec3} A line start\r\n\t* @param {vec3} B line end\r\n\t* @param {vec3} result to store result (optional)\r\n\t* @return {vec3} projectec point\r\n\t*/\r\n\tprojectPointOnLine: function( P, A, B, result )\r\n\t{\r\n\t\tresult = result || vec3.create();\r\n\t\t//A + dot(AP,AB) / dot(AB,AB) * AB\r\n\t\tvar AP = vec3.fromValues( P[0] - A[0], P[1] - A[1], P[2] - A[2]);\r\n\t\tvar AB = vec3.fromValues( B[0] - A[0], B[1] - A[1], B[2] - A[2]);\r\n\t\tvar div = vec3.dot(AP,AB) / vec3.dot(AB,AB);\r\n\t\tresult[0] = A[0] + div[0] * AB[0];\r\n\t\tresult[1] = A[1] + div[1] * AB[1];\r\n\t\tresult[2] = A[2] + div[2] * AB[2];\r\n\t\treturn result;\r\n\t},\r\n\r\n\t/**\r\n\t* Projects a 2D point on a 2D line\r\n\t* @method project2DPointOnLine\r\n\t* @param {vec2} P\r\n\t* @param {vec2} A line start\r\n\t* @param {vec2} B line end\r\n\t* @param {vec2} result to store result (optional)\r\n\t* @return {vec2} projectec point\r\n\t*/\r\n\tproject2DPointOnLine: function( P, A, B, result )\r\n\t{\r\n\t\tresult = result || vec2.create();\r\n\t\t//A + dot(AP,AB) / dot(AB,AB) * AB\r\n\t\tvar AP = vec2.fromValues(P[0] - A[0], P[1] - A[1]);\r\n\t\tvar AB = vec2.fromValues(B[0] - A[0], B[1] - A[1]);\r\n\t\tvar div = vec2.dot(AP,AB) / vec2.dot(AB,AB);\r\n\t\tresult[0] = A[0] + div[0] * AB[0];\r\n\t\tresult[1] = A[1] + div[1] * AB[1];\r\n\t\treturn result;\r\n\t},\r\n\r\n\t/**\r\n\t* Projects point on plane\r\n\t* @method projectPointOnPlane\r\n\t* @param {vec3} point\r\n\t* @param {vec3} P plane point\r\n\t* @param {vec3} N plane normal\r\n\t* @param {vec3} result to store result (optional)\r\n\t* @return {vec3} projectec point\r\n\t*/\r\n\tprojectPointOnPlane: function(point, P, N, result)\r\n\t{\r\n\t\tresult = result || vec3.create();\r\n\t\tvar v = vec3.subtract( vec3.create(), point, P );\r\n\t\tvar dist = vec3.dot(v,N);\r\n\t\treturn vec3.subtract( result, point , vec3.scale( vec3.create(), N, dist ) );\r\n\t},\r\n\r\n\t/**\r\n\t* Finds the reflected point over a plane (useful for reflecting camera position when rendering reflections)\r\n\t* @method reflectPointInPlane\r\n\t* @param {vec3} point point to reflect\r\n\t* @param {vec3} P point where the plane passes\r\n\t* @param {vec3} N normal of the plane\r\n\t* @return {vec3} reflected point\r\n\t*/\r\n\treflectPointInPlane: function(point, P, N)\r\n\t{\r\n\t\tvar d = -1 * (P[0] * N[0] + P[1] * N[1] + P[2] * N[2]);\r\n\t\tvar 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]);\r\n\t\t//trace(\"T:\" + t);\r\n\t\t//var closest = [ point[0]+t*N[0], point[1]+t*N[1], point[2]+t*N[2] ];\r\n\t\t//trace(\"Closest:\" + closest);\r\n\t\treturn vec3.fromValues( point[0]+t*N[0]*2, point[1]+t*N[1]*2, point[2]+t*N[2]*2 );\r\n\t},\r\n\r\n\t/**\r\n\t* test a ray plane collision and retrieves the collision point\r\n\t* @method testRayPlane\r\n\t* @param {vec3} start ray start\r\n\t* @param {vec3} direction ray direction\r\n\t* @param {vec3} P point where the plane passes\t\r\n\t* @param {vec3} N normal of the plane\r\n\t* @param {vec3} result collision position\r\n\t* @return {boolean} returns if the ray collides the plane or the ray is parallel to the plane\r\n\t*/\r\n\ttestRayPlane: function(start, direction, P, N, result)\r\n\t{\r\n\t\tvar D = vec3.dot( P, N );\r\n\t\tvar numer = D - vec3.dot(N, start);\r\n\t\tvar denom = vec3.dot(N, direction);\r\n\t\tif( Math.abs(denom) < EPSILON) return false;\r\n\t\tvar t = (numer / denom);\r\n\t\tif(t < 0.0) return false; //behind the ray\r\n\t\tif(result)\r\n\t\t\tvec3.add( result,  start, vec3.scale( result, direction, t) );\r\n\r\n\t\treturn true;\r\n\t},\r\n\r\n\t/**\r\n\t* test collision between segment and plane and retrieves the collision point\r\n\t* @method testSegmentPlane\r\n\t* @param {vec3} start segment start\r\n\t* @param {vec3} end segment end\r\n\t* @param {vec3} P point where the plane passes\t\r\n\t* @param {vec3} N normal of the plane\r\n\t* @param {vec3} result collision position\r\n\t* @return {boolean} returns if the segment collides the plane or it is parallel to the plane\r\n\t*/\r\n\ttestSegmentPlane: (function() { \r\n\t\tvar temp = vec3.create();\r\n\t\treturn function(start, end, P, N, result)\r\n\t\t{\r\n\t\t\tvar D = vec3.dot( P, N );\r\n\t\t\tvar numer = D - vec3.dot(N, start);\r\n\t\t\tvar direction = vec3.sub( temp, end, start );\r\n\t\t\tvar denom = vec3.dot(N, direction);\r\n\t\t\tif( Math.abs(denom) < EPSILON)\r\n\t\t\t\treturn false; //parallel \r\n\t\t\tvar t = (numer / denom);\r\n\t\t\tif(t < 0.0)\r\n\t\t\t\treturn false; //behind the start\r\n\t\t\tif(t > 1.0)\r\n\t\t\t\treturn false; //after the end\r\n\t\t\tif(result)\r\n\t\t\t\tvec3.add( result,  start, vec3.scale( result, direction, t) );\r\n\t\t\treturn true;\r\n\t\t};\r\n\t})(),\r\n\r\n\t/**\r\n\t* test a ray sphere collision and retrieves the collision point\r\n\t* @method testRaySphere\r\n\t* @param {vec3} start ray start\r\n\t* @param {vec3} direction ray direction (normalized)\r\n\t* @param {vec3} center center of the sphere\r\n\t* @param {number} radius radius of the sphere\r\n\t* @param {vec3} result [optional] collision position\r\n\t* @param {number} max_dist not fully tested\r\n\t* @return {boolean} returns if the ray collides the sphere\r\n\t*/\r\n\ttestRaySphere: (function() { \r\n\t\tvar temp = vec3.create();\r\n\t\treturn function(start, direction, center, radius, result, max_dist)\r\n\t\t{\r\n\t\t\t// sphere equation (centered at origin) x2+y2+z2=r2\r\n\t\t\t// ray equation x(t) = p0 + t*dir\r\n\t\t\t// substitute x(t) into sphere equation\r\n\t\t\t// solution below:\r\n\r\n\t\t\t// transform ray origin into sphere local coordinates\r\n\t\t\tvar orig = vec3.subtract( temp , start, center);\r\n\r\n\t\t\tvar a = direction[0]*direction[0] + direction[1]*direction[1] + direction[2]*direction[2];\r\n\t\t\tvar b = 2*orig[0]*direction[0] + 2*orig[1]*direction[1] + 2*orig[2]*direction[2];\r\n\t\t\tvar c = orig[0]*orig[0] + orig[1]*orig[1] + orig[2]*orig[2] - radius*radius;\r\n\t\t\t//return quadraticFormula(a,b,c,t0,t1) ? 2 : 0;\r\n\r\n\t\t\tvar q = b*b - 4*a*c; \r\n\t\t\tif( q < 0.0 )\r\n\t\t\t\treturn false;\r\n\r\n\t\t\tif(result)\r\n\t\t\t{\r\n\t\t\t\tvar sq = Math.sqrt(q);\r\n\t\t\t\tvar d = 1 / (2*a);\r\n\t\t\t\tvar r1 = ( -b + sq ) * d;\r\n\t\t\t\tvar r2 = ( -b - sq ) * d;\r\n\t\t\t\tvar t = r1 < r2 ? r1 : r2;\r\n\t\t\t\tif(max_dist !== undefined && t > max_dist)\r\n\t\t\t\t\treturn false;\r\n\t\t\t\tvec3.add(result, start, vec3.scale( result, direction, t ) );\r\n\t\t\t}\r\n\t\t\treturn true;//real roots\r\n\t\t};\r\n\t})(),\r\n\r\n\t/**\r\n\t* test a ray cylinder collision (only vertical cylinders) and retrieves the collision point [not fully tested]\r\n\t* @method testRayCylinder\r\n\t* @param {vec3} start ray start\r\n\t* @param {vec3} direction ray direction\r\n\t* @param {vec3} p center of the cylinder\r\n\t* @param {number} q height of the cylinder\r\n\t* @param {number} r radius of the cylinder\r\n\t* @param {vec3} result collision position\r\n\t* @return {boolean} returns if the ray collides the cylinder\r\n\t*/\r\n\ttestRayCylinder: function(start, direction, p, q, r, result)\r\n\t{\r\n\t\tvar sa = vec3.clone(start);\r\n\t\tvar sb = vec3.add(vec3.create(), start, vec3.scale( vec3.create(), direction, 100000) );\r\n\t\tvar t = 0;\r\n\t\tvar d = vec3.subtract(vec3.create(),q,p);\r\n\t\tvar m = vec3.subtract(vec3.create(),sa,p);\r\n\t\tvar n = vec3.subtract(vec3.create(),sb,sa);\r\n\t\t//var n = vec3.create(direction);\r\n\r\n\t\tvar md = vec3.dot(m, d);\r\n\t\tvar nd = vec3.dot(n, d);\r\n\t\tvar dd = vec3.dot(d, d);\r\n\r\n\t\t// Test if segment fully outside either endcap of cylinder\r\n\t\tif (md < 0.0 && md + nd < 0.0) return false; // Segment outside p side of cylinder\r\n\t\tif (md > dd && md + nd > dd) return false; // Segment outside q side of cylinder\r\n\r\n\t\tvar nn = vec3.dot(n, n);\r\n\t\tvar mn = vec3.dot(m, n);\r\n\t\tvar a = dd * nn - nd * nd; \r\n\t\tvar k = vec3.dot(m,m) - r*r;\r\n\t\tvar c = dd * k - md * md;\r\n\r\n\t\tif (Math.abs(a) < EPSILON) \r\n\t\t{\r\n\t\t\t// Segment runs parallel to cylinder axis\r\n\t\t\tif (c > 0.0) return false;\r\n\t\t\t// a and thus the segment lie outside cylinder\r\n\t\t\t// Now known that segment intersects cylinder; figure out how it intersects\r\n\t\t\tif (md < 0.0) t = -mn/nn;\r\n\t\t\t// Intersect segment against p endcap\r\n\t\t\telse if (md > dd)\r\n\t\t\t\tt=(nd-mn)/nn;\r\n\t\t\t// Intersect segment against q endcap\r\n\t\t\telse t = 0.0;\r\n\t\t\t// a lies inside cylinder\r\n\t\t\tif(result) \r\n\t\t\t\tvec3.add(result, sa, vec3.scale(result, n,t) );\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\tvar b = dd * mn - nd * md;\r\n\t\tvar discr = b*b - a*c;\r\n\t\tif (discr < 0.0) \r\n\t\t\treturn false;\r\n\t\t// No real roots; no intersection\r\n\t\tt = (-b - Math.sqrt(discr)) / a;\r\n\t\tif (t < 0.0 || t > 1.0) \r\n\t\t\treturn false;\r\n\t\t// Intersection lies outside segment\r\n\t\tif(md+t*nd < 0.0)\r\n\t\t{\r\n\t\t\t// Intersection outside cylinder on p side\r\n\t\t\tif (nd <= 0.0) \r\n\t\t\t\treturn false;\r\n\t\t\t// Segment pointing away from endcap\r\n\t\t\tt = -md / nd;\r\n\t\t\t// Keep intersection if Dot(S(t) - p, S(t) - p) <= r^2\r\n\t\t\tif(result) \r\n\t\t\t\tvec3.add(result, sa, vec3.scale(result, n,t) );\r\n\t\t\treturn k+2*t*(mn+t*nn) <= 0.0;\r\n\t\t} else if (md+t*nd>dd)\r\n\t\t{\r\n\t\t\t// Intersection outside cylinder on q side\r\n\t\t\tif (nd >= 0.0) return false; //Segment pointing away from endcap\r\n\t\t\tt = (dd - md) / nd;\r\n\t\t\t// Keep intersection if Dot(S(t) - q, S(t) - q) <= r^2\r\n\t\t\tif(result) \r\n\t\t\t\tvec3.add(result, sa, vec3.scale(result, n,t) );\r\n\t\t\treturn k+dd - 2*md+t*(2*(mn - nd)+t*nn) <= 0.0;\r\n\t\t}\r\n\t\t// Segment intersects cylinder between the endcaps; t is correct\r\n\t\tif(result)\r\n\t\t\tvec3.add(result, sa, vec3.scale(result, n,t) );\r\n\t\treturn true;\r\n\t},\r\n\r\n\r\n\t/**\r\n\t* test a ray bounding-box collision and retrieves the collision point, the BB must be Axis Aligned\r\n\t* @method testRayBox\r\n\t* @param {vec3} start ray start\r\n\t* @param {vec3} direction ray direction\r\n\t* @param {vec3} minB minimum position of the bounding box\r\n\t* @param {vec3} maxB maximim position of the bounding box\r\n\t* @param {vec3} result collision position\r\n\t* @return {boolean} returns if the ray collides the box\r\n\t*/\r\n\ttestRayBox: (function() { \r\n\t\r\n\t\tvar quadrant = new Float32Array(3);\r\n\t\tvar candidatePlane = new Float32Array(3);\r\n\t\tvar maxT = new Float32Array(3);\r\n\t\r\n\treturn function(start, direction, minB, maxB, result, max_dist)\r\n\t{\r\n\t\t//#define NUMDIM\t3\r\n\t\t//#define RIGHT\t\t0\r\n\t\t//#define LEFT\t\t1\r\n\t\t//#define MIDDLE\t2\r\n\r\n\t\tmax_dist = max_dist || Number.MAX_VALUE;\r\n\r\n\t\tvar inside = true;\r\n\t\tvar i = 0|0;\r\n\t\tvar whichPlane;\r\n\t\t\r\n\t\tquadrant.fill(0);\r\n\t\tmaxT.fill(0);\r\n\t\tcandidatePlane.fill(0);\r\n\r\n\t\t/* Find candidate planes; this loop can be avoided if\r\n\t\trays cast all from the eye(assume perpsective view) */\r\n\t\tfor (i=0; i < 3; ++i)\r\n\t\t\tif(start[i] < minB[i]) {\r\n\t\t\t\tquadrant[i] = 1;\r\n\t\t\t\tcandidatePlane[i] = minB[i];\r\n\t\t\t\tinside = false;\r\n\t\t\t}else if (start[i] > maxB[i]) {\r\n\t\t\t\tquadrant[i] = 0;\r\n\t\t\t\tcandidatePlane[i] = maxB[i];\r\n\t\t\t\tinside = false;\r\n\t\t\t}else\t{\r\n\t\t\t\tquadrant[i] = 2;\r\n\t\t\t}\r\n\r\n\t\t/* Ray origin inside bounding box */\r\n\t\tif(inside)\t{\r\n\t\t\tif(result)\r\n\t\t\t\tvec3.copy(result, start);\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\r\n\t\t/* Calculate T distances to candidate planes */\r\n\t\tfor (i = 0; i < 3; ++i)\r\n\t\t\tif (quadrant[i] != 2 && direction[i] != 0.)\r\n\t\t\t\tmaxT[i] = (candidatePlane[i] - start[i]) / direction[i];\r\n\t\t\telse\r\n\t\t\t\tmaxT[i] = -1.;\r\n\r\n\t\t/* Get largest of the maxT's for final choice of intersection */\r\n\t\twhichPlane = 0;\r\n\t\tfor (i = 1; i < 3; i++)\r\n\t\t\tif (maxT[whichPlane] < maxT[i])\r\n\t\t\t\twhichPlane = i;\r\n\r\n\t\t/* Check final candidate actually inside box */\r\n\t\tif (maxT[whichPlane] < 0.) return false;\r\n\t\tif (maxT[whichPlane] > max_dist) return false; //NOT TESTED\r\n\r\n\t\tfor (i = 0; i < 3; ++i)\r\n\t\t\tif (whichPlane != i) {\r\n\t\t\t\tvar res = start[i] + maxT[whichPlane] * direction[i];\r\n\t\t\t\tif (res < minB[i] || res > maxB[i])\r\n\t\t\t\t\treturn false;\r\n\t\t\t\tif(result)\r\n\t\t\t\t\tresult[i] = res;\r\n\t\t\t} else {\r\n\t\t\t\tif(result)\r\n\t\t\t\t\tresult[i] = candidatePlane[i];\r\n\t\t\t}\r\n\t\treturn true;\t\t\t\t/* ray hits box */\r\n\t}\r\n\t})(),\t\r\n\r\n\t/**\r\n\t* test a ray bounding-box collision, it uses the  BBox class and allows to use non-axis aligned bbox\r\n\t* @method testRayBBox\r\n\t* @param {vec3} origin ray origin\r\n\t* @param {vec3} direction ray direction\r\n\t* @param {BBox} box in BBox format\r\n\t* @param {mat4} model transformation of the BBox [optional]\r\n\t* @param {vec3} result collision position in world space unless in_local is true\r\n\t* @return {boolean} returns if the ray collides the box\r\n\t*/\r\n\ttestRayBBox: (function(){ \r\n\tvar inv = mat4.create();\t\r\n\tvar end = vec3.create();\r\n\tvar origin2 = vec3.create();\r\n\treturn function( origin, direction, box, model, result, max_dist, in_local )\r\n\t{\r\n\t\tif(!origin || !direction || !box)\r\n\t\t\tthrow(\"parameters missing\");\r\n\t\tif(model)\r\n\t\t{\r\n\t\t\tmat4.invert( inv, model );\r\n\t\t\tvec3.add( end, origin, direction );\r\n\t\t\torigin = vec3.transformMat4( origin2, origin, inv);\r\n\t\t\tvec3.transformMat4( end, end, inv );\r\n\t\t\tvec3.sub( end, end, origin );\r\n\t\t\tdirection = vec3.normalize( end, end );\r\n\t\t}\r\n\t\tvar r = this.testRayBox( origin, direction, box.subarray(6,9), box.subarray(9,12), result, max_dist );\r\n\t\tif(!in_local && model && result)\r\n\t\t\tvec3.transformMat4(result, result, model);\r\n\t\treturn r;\r\n\t}\r\n\t})(),\r\n\r\n\t/**\r\n\t* test if a 3d point is inside a BBox\r\n\t* @method testPointBBox\r\n\t* @param {vec3} point\r\n\t* @param {BBox} bbox\r\n\t* @return {boolean} true if it is inside\r\n\t*/\r\n\ttestPointBBox: function(point, bbox) {\r\n\t\tif(point[0] < bbox[6] || point[0] > bbox[9] ||\r\n\t\t\tpoint[1] < bbox[7] || point[0] > bbox[10] ||\r\n\t\t\tpoint[2] < bbox[8] || point[0] > bbox[11])\r\n\t\t\treturn false;\r\n\t\treturn true;\r\n\t},\r\n\r\n\t/**\r\n\t* test if a BBox overlaps another BBox\r\n\t* @method testBBoxBBox\r\n\t* @param {BBox} a\r\n\t* @param {BBox} b\r\n\t* @return {boolean} true if it overlaps\r\n\t*/\r\n\ttestBBoxBBox: function(a, b) \r\n\t{\r\n\t\tvar tx =  Math.abs( b[0] - a[0]);\r\n\t\tif (tx > (a[3] + b[3]))\r\n\t\t\treturn false; //outside\r\n\t\tvar ty =  Math.abs(b[1] - a[1]);\r\n\t\tif (ty > (a[4] + b[4]))\r\n\t\t\treturn false; //outside\r\n\t\tvar tz =  Math.abs( b[2] - a[2]);\r\n\t\tif (tz > (a[5] + b[5]) )\r\n\t\t\treturn false; //outside\r\n\r\n\t\tvar vmin = BBox.getMin(b);\r\n\t\tif ( geo.testPointBBox(vmin, a) )\r\n\t\t{\r\n\t\t\tvar vmax = BBox.getMax(b);\r\n\t\t\tif (geo.testPointBBox(vmax, a))\r\n\t\t\t{\r\n\t\t\t\treturn true;// INSIDE;// this instance contains b\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn true; //OVERLAP; // this instance  overlaps with b\r\n\t},\r\n\r\n\t/**\r\n\t* test if a sphere overlaps a BBox\r\n\t* @method testSphereBBox\r\n\t* @param {vec3} point\r\n\t* @param {float} radius\r\n\t* @param {BBox} bounding_box\r\n\t* @return {boolean} true if it overlaps\r\n\t*/\r\n\ttestSphereBBox: function(center, radius, bbox) \r\n\t{\r\n\t\t// arvo's algorithm from gamasutra\r\n\t\t// http://www.gamasutra.com/features/19991018/Gomez_4.htm\r\n\r\n\t\tvar s, d = 0.0;\r\n\t\t//find the square of the distance\r\n\t\t//from the sphere to the box\r\n\t\tvar vmin = BBox.getMin( bbox );\r\n\t\tvar vmax = BBox.getMax( bbox );\r\n\t\tfor(var i = 0; i < 3; ++i) \r\n\t\t{ \r\n\t\t\tif( center[i] < vmin[i] )\r\n\t\t\t{\r\n\t\t\t\ts = center[i] - vmin[i];\r\n\t\t\t\td += s*s; \r\n\t\t\t}\r\n\t\t\telse if( center[i] > vmax[i] )\r\n\t\t\t{ \r\n\t\t\t\ts = center[i] - vmax[i];\r\n\t\t\t\td += s*s; \r\n\t\t\t}\r\n\t\t}\r\n\t\t//return d <= r*r\r\n\r\n\t\tvar radiusSquared = radius * radius;\r\n\t\tif (d <= radiusSquared)\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t\t/*\r\n\t\t\t// this is used just to know if it overlaps or is just inside, but I dont care\r\n\t\t\t// make an aabb aabb test with the sphere aabb to test inside state\r\n\t\t\tvar halfsize = vec3.fromValues( radius, radius, radius );\r\n\t\t\tvar sphere_bbox = BBox.fromCenterHalfsize( center, halfsize );\r\n\t\t\tif ( geo.testBBoxBBox(bbox, sphere_bbox) )\r\n\t\t\t\treturn INSIDE;\r\n\t\t\treturn OVERLAP;\t\r\n\t\t\t*/\r\n\t\t}\r\n\r\n\t\treturn false; //OUTSIDE;\r\n\t},\r\n\r\n\tclosestPointBetweenLines: function(a0,a1, b0,b1, p_a, p_b)\r\n\t{\r\n\t\tvar u = vec3.subtract( vec3.create(), a1, a0 );\r\n\t\tvar v = vec3.subtract( vec3.create(), b1, b0 );\r\n\t\tvar w = vec3.subtract( vec3.create(), a0, b0 );\r\n\r\n\t\tvar a = vec3.dot(u,u);         // always >= 0\r\n\t\tvar b = vec3.dot(u,v);\r\n\t\tvar c = vec3.dot(v,v);         // always >= 0\r\n\t\tvar d = vec3.dot(u,w);\r\n\t\tvar e = vec3.dot(v,w);\r\n\t\tvar D = a*c - b*b;        // always >= 0\r\n\t\tvar sc, tc;\r\n\r\n\t\t// compute the line parameters of the two closest points\r\n\t\tif (D < EPSILON) {          // the lines are almost parallel\r\n\t\t\tsc = 0.0;\r\n\t\t\ttc = (b>c ? d/b : e/c);    // use the largest denominator\r\n\t\t}\r\n\t\telse {\r\n\t\t\tsc = (b*e - c*d) / D;\r\n\t\t\ttc = (a*e - b*d) / D;\r\n\t\t}\r\n\r\n\t\t// get the difference of the two closest points\r\n\t\tif(p_a)\tvec3.add(p_a, a0, vec3.scale(vec3.create(),u,sc));\r\n\t\tif(p_b)\tvec3.add(p_b, b0, vec3.scale(vec3.create(),v,tc));\r\n\r\n\t\tvar 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)\r\n\t\treturn vec3.length(dP);   // return the closest distance\r\n\t},\r\n\r\n\t/**\r\n\t* extract frustum planes given a view-projection matrix\r\n\t* @method extractPlanes\r\n\t* @param {mat4} viewprojection matrix\r\n\t* @return {Float32Array} returns all 6 planes in a float32array[24]\r\n\t*/\r\n\textractPlanes: function(vp, planes)\r\n\t{\r\n\t\tvar planes = planes || new Float32Array(4*6);\r\n\r\n\t\t//right\r\n\t\tplanes.set( [vp[3] - vp[0], vp[7] - vp[4], vp[11] - vp[8], vp[15] - vp[12] ], 0); \r\n\t\tnormalize(0);\r\n\r\n\t\t//left\r\n\t\tplanes.set( [vp[3] + vp[0], vp[ 7] + vp[ 4], vp[11] + vp[ 8], vp[15] + vp[12] ], 4);\r\n\t\tnormalize(4);\r\n\r\n\t\t//bottom\r\n\t\tplanes.set( [ vp[ 3] + vp[ 1], vp[ 7] + vp[ 5], vp[11] + vp[ 9], vp[15] + vp[13] ], 8);\r\n\t\tnormalize(8);\r\n\r\n\t\t//top\r\n\t\tplanes.set( [ vp[ 3] - vp[ 1], vp[ 7] - vp[ 5], vp[11] - vp[ 9], vp[15] - vp[13] ],12);\r\n\t\tnormalize(12);\r\n\r\n\t\t//back\r\n\t\tplanes.set( [ vp[ 3] - vp[ 2], vp[ 7] - vp[ 6], vp[11] - vp[10], vp[15] - vp[14] ],16);\r\n\t\tnormalize(16);\r\n\r\n\t\t//front\r\n\t\tplanes.set( [ vp[ 3] + vp[ 2], vp[ 7] + vp[ 6], vp[11] + vp[10], vp[15] + vp[14] ],20);\r\n\t\tnormalize(20);\r\n\r\n\t\treturn planes;\r\n\r\n\t\tfunction normalize(pos)\r\n\t\t{\r\n\t\t\tvar N = planes.subarray(pos,pos+3);\r\n\t\t\tvar l = vec3.length(N);\r\n\t\t\tif(l === 0) return;\r\n\t\t\tl = 1.0 / l;\r\n\t\t\tplanes[pos] *= l;\r\n\t\t\tplanes[pos+1] *= l;\r\n\t\t\tplanes[pos+2] *= l;\r\n\t\t\tplanes[pos+3] *= l;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t* test a BBox against the frustum\r\n\t* @method frustumTestBox\r\n\t* @param {Float32Array} planes frustum planes\r\n\t* @param {BBox} boundindbox in BBox format\r\n\t* @return {enum} CLIP_INSIDE, CLIP_OVERLAP, CLIP_OUTSIDE\r\n\t*/\r\n\tfrustumTestBox: function(planes, box)\r\n\t{\r\n\t\tvar flag = 0, o = 0;\r\n\r\n\t\tflag = planeBoxOverlap(planes.subarray(0,4),box);\r\n\t\tif (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag;\r\n\t\tflag =  planeBoxOverlap(planes.subarray(4,8),box);\r\n\t\tif (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag;\r\n\t\tflag =  planeBoxOverlap(planes.subarray(8,12),box);\r\n\t\tif (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag;\r\n\t\tflag =  planeBoxOverlap(planes.subarray(12,16),box);\r\n\t\tif (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag;\r\n\t\tflag =  planeBoxOverlap(planes.subarray(16,20),box);\r\n\t\tif (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag;\r\n\t\tflag =  planeBoxOverlap(planes.subarray(20,24),box);\r\n\t\tif (flag == CLIP_OUTSIDE) return CLIP_OUTSIDE; o+= flag;\r\n\r\n\t\treturn o == 0 ? CLIP_INSIDE : CLIP_OVERLAP;\r\n\t},\r\n\r\n\t/**\r\n\t* test a Sphere against the frustum\r\n\t* @method frustumTestSphere\r\n\t* @param {vec3} center sphere center\r\n\t* @param {number} radius sphere radius\r\n\t* @return {enum} CLIP_INSIDE, CLIP_OVERLAP, CLIP_OUTSIDE\r\n\t*/\r\n\r\n\tfrustumTestSphere: function(planes, center, radius)\r\n\t{\r\n\t\tvar dist;\r\n\t\tvar overlap = false;\r\n\r\n\t\tdist = distanceToPlane( planes.subarray(0,4), center );\r\n\t\tif( dist < -radius ) return CLIP_OUTSIDE;\r\n\t\telse if(dist >= -radius && dist <= radius)\toverlap = true;\r\n\t\tdist = distanceToPlane( planes.subarray(4,8), center );\r\n\t\tif( dist < -radius ) return CLIP_OUTSIDE;\r\n\t\telse if(dist >= -radius && dist <= radius)\toverlap = true;\r\n\t\tdist = distanceToPlane( planes.subarray(8,12), center );\r\n\t\tif( dist < -radius ) return CLIP_OUTSIDE;\r\n\t\telse if(dist >= -radius && dist <= radius)\toverlap = true;\r\n\t\tdist = distanceToPlane( planes.subarray(12,16), center );\r\n\t\tif( dist < -radius ) return CLIP_OUTSIDE;\r\n\t\telse if(dist >= -radius && dist <= radius)\toverlap = true;\r\n\t\tdist = distanceToPlane( planes.subarray(16,20), center );\r\n\t\tif( dist < -radius ) return CLIP_OUTSIDE;\r\n\t\telse if(dist >= -radius && dist <= radius)\toverlap = true;\r\n\t\tdist = distanceToPlane( planes.subarray(20,24), center );\r\n\t\tif( dist < -radius ) return CLIP_OUTSIDE;\r\n\t\telse if(dist >= -radius && dist <= radius)\toverlap = true;\r\n\t\treturn overlap ? CLIP_OVERLAP : CLIP_INSIDE;\r\n\t},\r\n\r\n\r\n\t/**\r\n\t* test if a 2d point is inside a 2d polygon\r\n\t* @method testPoint2DInPolygon\r\n\t* @param {Array} poly array of 2d points\r\n\t* @param {vec2} point\r\n\t* @return {boolean} true if it is inside\r\n\t*/\r\n\ttestPoint2DInPolygon: function(poly, pt) {\r\n    for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)\r\n        ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1] < poly[i][1]))\r\n        && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])\r\n        && (c = !c);\r\n    return c;\r\n\t}\r\n};\r\n\r\n/**\r\n* BBox is a class to create BoundingBoxes but it works as glMatrix, creating Float32Array with the info inside instead of objects\r\n* The bounding box is stored as center,halfsize,min,max,radius (total of 13 floats)\r\n* @class BBox\r\n*/\r\nglobal.BBox = GL.BBox = {\r\n\tcenter:0,\r\n\thalfsize:3,\r\n\tmin:6,\r\n\tmax:9,\r\n\tradius:12,\r\n\tdata_length: 13,\r\n\t\r\n\t//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 ]),\r\n\tcorners: [ 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) ] ,\r\n\r\n\t/**\r\n\t* create an empty bbox\r\n\t* @method create\r\n\t* @return {BBox} returns a float32array with the bbox\r\n\t*/\r\n\tcreate: function()\r\n\t{\r\n\t\treturn new Float32Array(13);\r\n\t},\r\n\r\n\t/**\r\n\t* create an bbox copy from another one\r\n\t* @method clone\r\n\t* @return {BBox} returns a float32array with the bbox\r\n\t*/\r\n\tclone: function(bb)\r\n\t{\r\n\t\treturn new Float32Array(bb);\r\n\t},\r\n\r\n\t/**\r\n\t* copy one bbox into another\r\n\t* @method copy\r\n\t* @param {BBox} out where to store the result\r\n\t* @param {BBox} where to read the bbox\r\n\t* @return {BBox} returns out\r\n\t*/\r\n\tcopy: function(out,bb)\r\n\t{\r\n\t\tout.set(bb);\r\n\t\treturn out;\r\n\t},\t\r\n\r\n\t/**\r\n\t* create a bbox from one point\r\n\t* @method fromPoint\r\n\t* @param {vec3} point\r\n\t* @return {BBox} returns a float32array with the bbox\r\n\t*/\r\n\tfromPoint: function(point)\r\n\t{\r\n\t\tvar bb = this.create();\r\n\t\tbb.set(point, 0); //center\r\n\t\tbb.set(point, 6); //min\r\n\t\tbb.set(point, 9); //max\r\n\t\treturn bb;\r\n\t},\r\n\r\n\t/**\r\n\t* create a bbox from min and max points\r\n\t* @method fromMinMax\r\n\t* @param {vec3} min\r\n\t* @param {vec3} max\r\n\t* @return {BBox} returns a float32array with the bbox\r\n\t*/\r\n\tfromMinMax: function(min,max)\r\n\t{\r\n\t\tvar bb = this.create();\r\n\t\tthis.setMinMax(bb, min, max);\r\n\t\treturn bb;\r\n\t},\r\n\r\n\t/**\r\n\t* create a bbox from center and halfsize\r\n\t* @method fromCenterHalfsize\r\n\t* @param {vec3} center\r\n\t* @param {vec3} halfsize\r\n\t* @return {BBox} returns a float32array with the bbox\r\n\t*/\r\n\tfromCenterHalfsize: function(center, halfsize)\r\n\t{\r\n\t\tvar bb = this.create();\r\n\t\tthis.setCenterHalfsize(bb, center, halfsize);\r\n\t\treturn bb;\r\n\t},\r\n\r\n\t/**\r\n\t* create a bbox from a typed-array containing points\r\n\t* @method fromPoints\r\n\t* @param {Float32Array} points\r\n\t* @return {BBox} returns a float32array with the bbox\r\n\t*/\r\n\tfromPoints: function(points)\r\n\t{\r\n\t\tvar bb = this.create();\r\n\t\tthis.setFromPoints(bb, points);\r\n\t\treturn bb;\t\r\n\t},\r\n\r\n\t/**\r\n\t* set the values to a BB from a set of points\r\n\t* @method setFromPoints\r\n\t* @param {BBox} out where to store the result\r\n\t* @param {Float32Array} points\r\n\t* @return {BBox} returns a float32array with the bbox\r\n\t*/\r\n\tsetFromPoints: function(bb, points)\r\n\t{\r\n\t\tvar min = bb.subarray(6,9);\r\n\t\tvar max = bb.subarray(9,12);\r\n\r\n\t\tmin[0] = points[0]; //min.set( points.subarray(0,3) );\r\n\t\tmin[1] = points[1];\r\n\t\tmin[2] = points[2];\r\n\t\tmax.set( min );\r\n\r\n\t\tvar v = 0;\r\n\t\tfor(var i = 3, l = points.length; i < l; i+=3)\r\n\t\t{\r\n\t\t\tvar x = points[i];\r\n\t\t\tvar y = points[i+1];\r\n\t\t\tvar z = points[i+2];\r\n\t\t\tif( x < min[0] ) min[0] = x;\r\n\t\t\telse if( x > max[0] ) max[0] = x;\r\n\t\t\tif( y < min[1] ) min[1] = y;\r\n\t\t\telse if( y > max[1] ) max[1] = y;\r\n\t\t\tif( z < min[2] ) min[2] = z;\r\n\t\t\telse if( z > max[2] ) max[2] = z;\r\n\t\t\t/*\r\n\t\t\tv = points.subarray(i,i+3);\r\n\t\t\tvec3.min( min, v, min);\r\n\t\t\tvec3.max( max, v, max);\r\n\t\t\t*/\r\n\t\t}\r\n\r\n\t\t//center\r\n\t\tbb[0] = (min[0] + max[0]) * 0.5;\r\n\t\tbb[1] = (min[1] + max[1]) * 0.5;\r\n\t\tbb[2] = (min[2] + max[2]) * 0.5;\r\n\t\t//halfsize\r\n\t\tbb[3] = max[0] - bb[0];\r\n\t\tbb[4] = max[1] - bb[1];\r\n\t\tbb[5] = max[2] - bb[2];\r\n\t\tbb[12] = Math.sqrt( bb[3]*bb[3] + bb[4]*bb[4] + bb[5]*bb[5] );\r\n\r\n\t\t/*\r\n\t\tvar center = vec3.add( bb.subarray(0,3), min, max );\r\n\t\tvec3.scale( center, center, 0.5);\r\n\t\tvec3.subtract( bb.subarray(3,6), max, center );\r\n\t\tbb[12] = vec3.length(bb.subarray(3,6)); //radius\t\t\r\n\t\t*/\r\n\t\treturn bb;\r\n\t},\r\n\r\n\t/**\r\n\t* set the values to a BB from min and max\r\n\t* @method setMinMax\r\n\t* @param {BBox} out where to store the result\r\n\t* @param {vec3} min\r\n\t* @param {vec3} max\r\n\t* @return {BBox} returns out\r\n\t*/\r\n\tsetMinMax: function(bb, min, max)\r\n\t{\r\n\t\tbb[6] = min[0];\r\n\t\tbb[7] = min[1];\r\n\t\tbb[8] = min[2];\r\n\t\tbb[9] = max[0];\r\n\t\tbb[10] = max[1];\r\n\t\tbb[11] = max[2];\r\n\r\n\t\t//halfsize\r\n\t\tvar halfsize = bb.subarray(3,6); \r\n\t\tvec3.sub( halfsize, max, min ); //range\r\n\t\tvec3.scale( halfsize, halfsize, 0.5 );\r\n\r\n\t\t//center\r\n\t\tbb[0] = max[0] - halfsize[0];\r\n\t\tbb[1] = max[1] - halfsize[1];\r\n\t\tbb[2] = max[2] - halfsize[2];\r\n\r\n\t\tbb[12] = vec3.length(bb.subarray(3,6)); //radius\r\n\t\treturn bb;\r\n\t},\r\n\r\n\t/**\r\n\t* set the values to a BB from center and halfsize\r\n\t* @method setCenterHalfsize\r\n\t* @param {BBox} out where to store the result\r\n\t* @param {vec3} min\r\n\t* @param {vec3} max\r\n\t* @param {number} radius [optional] (the minimum distance from the center to the further point)\r\n\t* @return {BBox} returns out\r\n\t*/\r\n\tsetCenterHalfsize: function(bb, center, halfsize, radius)\r\n\t{\r\n\t\tbb[0] = center[0];\r\n\t\tbb[1] = center[1];\r\n\t\tbb[2] = center[2];\r\n\t\tbb[3] = halfsize[0];\r\n\t\tbb[4] = halfsize[1];\r\n\t\tbb[5] = halfsize[2];\r\n\t\tbb[6] = bb[0] - bb[3];\r\n\t\tbb[7] = bb[1] - bb[4];\r\n\t\tbb[8] = bb[2] - bb[5];\r\n\t\tbb[9] = bb[0] + bb[3];\r\n\t\tbb[10] = bb[1] + bb[4];\r\n\t\tbb[11] = bb[2] + bb[5];\r\n\t\tif(radius)\r\n\t\t\tbb[12] = radius;\r\n\t\telse\r\n\t\t\tbb[12] = vec3.length(halfsize);\r\n\t\treturn bb;\r\n\t},\r\n\r\n\t/**\r\n\t* Apply a matrix transformation to the BBox (applies to every corner and recomputes the BB)\r\n\t* @method transformMat4\r\n\t* @param {BBox} out where to store the result\r\n\t* @param {BBox} bb bbox you want to transform\r\n\t* @param {mat4} mat transformation\r\n\t* @return {BBox} returns out\r\n\t*/\r\n\ttransformMat4: (function(){\r\n\t\tvar hsx = 0;\r\n\t\tvar hsy = 0;\r\n\t\tvar hsz = 0;\r\n\t\tvar points_buffer = new Float32Array(8*3);\r\n\t\tvar points = [];\r\n\t\tfor(var i = 0; i < 24; i += 3 )\r\n\t\t\tpoints.push( points_buffer.subarray( i, i+3 ) );\r\n\t\t\r\n\t\treturn function( out, bb, mat )\r\n\t\t{\r\n\t\t\tvar centerx = bb[0];\r\n\t\t\tvar centery = bb[1];\r\n\t\t\tvar centerz = bb[2];\r\n\t\t\thsx = bb[3];\r\n\t\t\thsy = bb[4];\r\n\t\t\thsz = bb[5];\r\n\r\n\t\t\tvar corners = this.corners;\r\n\r\n\t\t\tfor(var i = 0; i < 8; ++i)\t\t\r\n\t\t\t{\r\n\t\t\t\tvar corner = corners[i];\r\n\t\t\t\tvar result = points[i];\r\n\t\t\t\tresult[0] = hsx * corner[0] + centerx;\r\n\t\t\t\tresult[1] = hsy * corner[1] + centery;\r\n\t\t\t\tresult[2] = hsz * corner[2] + centerz;\r\n\t\t\t\tmat4.multiplyVec3( result, mat, result );\r\n\t\t\t}\r\n\r\n\t\t\treturn this.setFromPoints( out, points_buffer );\r\n\t\t}\r\n\t})(),\r\n\r\n\r\n\t/**\r\n\t* Computes the eight corners of the BBox and returns it\r\n\t* @method getCorners\r\n\t* @param {BBox} bb the bounding box\r\n\t* @param {Float32Array} result optional, should be 8 * 3\r\n\t* @return {Float32Array} returns the 8 corners\r\n\t*/\r\n\tgetCorners: function( bb, result )\r\n\t{\r\n\t\tvar center = bb; //.subarray(0,3); AVOID GC\r\n\t\tvar halfsize = bb.subarray(3,6);\r\n\r\n\t\tvar corners = null;\r\n\t\tif(result)\r\n\t\t{\r\n\t\t\tresult.set(this.corners);\r\n\t\t\tcorners = result;\r\n\t\t}\r\n\t\telse\r\n\t\t\tcorners = new Float32Array( this.corners );\r\n\r\n\t\tfor(var i = 0; i < 8; ++i)\t\t\r\n\t\t{\r\n\t\t\tvar corner = corners.subarray(i*3, i*3+3);\r\n\t\t\tvec3.multiply( corner, halfsize, corner );\r\n\t\t\tvec3.add( corner, corner, center );\r\n\t\t}\r\n\r\n\t\treturn corners;\r\n\t},\t\r\n\r\n\tmerge: function( out, a, b )\r\n\t{\r\n\t\tvar min = out.subarray(6,9);\r\n\t\tvar max = out.subarray(9,12);\r\n\t\tvec3.min( min, a.subarray(6,9), b.subarray(6,9) );\r\n\t\tvec3.max( max, a.subarray(9,12), b.subarray(9,12) );\r\n\t\treturn BBox.setMinMax( out, min, max );\r\n\t},\r\n\r\n\textendToPoint: function( out, p )\r\n\t{\r\n\t\tif( p[0] < out[6] )\tout[6] = p[0];\r\n\t\telse if( p[0] > out[9] ) out[9] = p[0];\r\n\r\n\t\tif( p[1] < out[7] )\tout[7] = p[1];\r\n\t\telse if( p[1] > out[10] ) out[10] = p[1];\r\n\r\n\r\n\t\tif( p[2] < out[8] )\tout[8] = p[2];\r\n\t\telse if( p[2] > out[11] ) out[11] = p[2];\r\n\r\n\t\t//recompute \r\n\t\tvar min = out.subarray(6,9);\r\n\t\tvar max = out.subarray(9,12);\r\n\t\tvar center = vec3.add( out.subarray(0,3), min, max );\r\n\t\tvec3.scale( center, center, 0.5);\r\n\t\tvec3.subtract( out.subarray(3,6), max, center );\r\n\t\tout[12] = vec3.length( out.subarray(3,6) ); //radius\t\t\r\n\t\treturn out;\r\n\t},\r\n\r\n\tclampPoint: function(out, box, point)\r\n\t{\r\n\t\tout[0] = Math.clamp( point[0], box[0] - box[3], box[0] + box[3]);\r\n\t\tout[1] = Math.clamp( point[1], box[1] - box[4], box[1] + box[4]);\r\n\t\tout[2] = Math.clamp( point[2], box[2] - box[5], box[2] + box[5]);\r\n\t},\r\n\r\n\tisPointInside: function( bbox, point )\r\n\t{\r\n\t\tif( (bbox[0] - bbox[3]) > point[0] ||\r\n\t\t\t(bbox[1] - bbox[4]) > point[1] ||\r\n\t\t\t(bbox[2] - bbox[5]) > point[2] ||\r\n\t\t\t(bbox[0] + bbox[3]) < point[0] ||\r\n\t\t\t(bbox[1] + bbox[4]) < point[1] ||\r\n\t\t\t(bbox[2] + bbox[5]) < point[2] )\r\n\t\t\treturn false;\r\n\t\treturn true;\r\n\t},\r\n\r\n\tgetCenter: function(bb) { return bb.subarray(0,3); },\r\n\tgetHalfsize: function(bb) { return bb.subarray(3,6); },\r\n\tgetMin: function(bb) { return bb.subarray(6,9); },\r\n\tgetMax: function(bb) { return bb.subarray(9,12); },\r\n\tgetRadius: function(bb) { return bb[12]; }\r\n\t//setCenter,setHalfsize not coded, too much work to update all\r\n}\r\n\r\nglobal.distanceToPlane = GL.distanceToPlane = function distanceToPlane(plane, point)\r\n{\r\n\treturn vec3.dot(plane,point) + plane[3];\r\n}\r\n\r\nglobal.planeBoxOverlap = GL.planeBoxOverlap = function planeBoxOverlap(plane, box)\r\n{\r\n\tvar n = plane; //.subarray(0,3); \r\n\tvar d = plane[3];\r\n\t//hack, to avoif GC I use indices directly\r\n\tvar center = box; //.subarray(0,3);\r\n\tvar halfsize = box; //.subarray(3,6);\r\n\r\n\tvar radius = Math.abs( halfsize[3] * n[0] ) + Math.abs( halfsize[4] * n[1] ) + Math.abs( halfsize[5] * n[2] );\r\n\tvar distance = vec3.dot(n,center) + d;\r\n\r\n\tif (distance <= -radius)\r\n\t\treturn CLIP_OUTSIDE;\r\n\telse if (distance <= radius)\r\n\t\treturn CLIP_OVERLAP;\r\n\treturn CLIP_INSIDE;\r\n}\r\n\n/**\r\n* @namespace GL\r\n*/\r\n\r\n/**\r\n*   Octree generator for fast ray triangle collision with meshes\r\n*\tDependencies: glmatrix.js (for vector and matrix operations)\r\n* @class Octree\r\n* @constructor\r\n* @param {Mesh} mesh object containing vertices buffer (indices buffer optional)\r\n*/\r\n\r\nglobal.Octree = GL.Octree = function Octree( mesh )\r\n{\r\n\tthis.root = null;\r\n\tthis.total_depth = 0;\r\n\tthis.total_nodes = 0;\r\n\tif(mesh)\r\n\t{\r\n\t\tthis.buildFromMesh(mesh);\r\n\t\tthis.total_nodes = this.trim();\r\n\t}\r\n}\r\n\r\nOctree.MAX_NODE_TRIANGLES_RATIO = 0.1;\r\nOctree.MAX_OCTREE_DEPTH = 8;\r\nOctree.OCTREE_MARGIN_RATIO = 0.01;\r\nOctree.OCTREE_MIN_MARGIN = 0.1;\r\n\r\nvar octree_tested_boxes = 0;\r\nvar octree_tested_triangles = 0;\r\n\r\nOctree.prototype.buildFromMesh = function( mesh )\r\n{\r\n\tthis.total_depth = 0;\r\n\tthis.total_nodes = 0;\r\n\r\n\tvar vertices = mesh.getBuffer(\"vertices\").data;\r\n\tvar triangles = mesh.getIndexBuffer(\"triangles\");\r\n\tif(triangles) \r\n\t\ttriangles = triangles.data; //get the internal data\r\n\r\n\tvar root = this.computeAABB(vertices);\r\n\tthis.root = root;\r\n\tthis.total_nodes = 1;\r\n\tthis.total_triangles = triangles ? triangles.length / 3 : vertices.length / 9;\r\n\tthis.max_node_triangles = this.total_triangles * Octree.MAX_NODE_TRIANGLES_RATIO;\r\n\r\n\tvar margin = vec3.create();\r\n\tvec3.scale( margin, root.size, Octree.OCTREE_MARGIN_RATIO );\r\n\tif(margin[0] < Octree.OCTREE_MIN_MARGIN) margin[0] = Octree.OCTREE_MIN_MARGIN;\r\n\tif(margin[1] < Octree.OCTREE_MIN_MARGIN) margin[1] = Octree.OCTREE_MIN_MARGIN;\r\n\tif(margin[2] < Octree.OCTREE_MIN_MARGIN) margin[2] = Octree.OCTREE_MIN_MARGIN;\r\n\r\n\tvec3.sub(root.min, root.min, margin);\r\n\tvec3.add(root.max, root.max, margin);\r\n\r\n\troot.faces = [];\r\n\troot.inside = 0;\r\n\r\n\r\n\t//indexed\r\n\tif(triangles)\r\n\t{\r\n\t\tfor(var i = 0; i < triangles.length; i+=3)\r\n\t\t{\r\n\t\t\tvar face = new Float32Array([vertices[triangles[i]*3], vertices[triangles[i]*3+1],vertices[triangles[i]*3+2],\r\n\t\t\t\t\t\tvertices[triangles[i+1]*3], vertices[triangles[i+1]*3+1],vertices[triangles[i+1]*3+2],\r\n\t\t\t\t\t\tvertices[triangles[i+2]*3], vertices[triangles[i+2]*3+1],vertices[triangles[i+2]*3+2],i/3]);\r\n\t\t\tthis.addToNode( face,root,0);\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tfor(var i = 0; i < vertices.length; i+=9)\r\n\t\t{\r\n\t\t\tvar face = new Float32Array( 10 );\r\n\t\t\tface.set( vertices.subarray(i,i+9) );\r\n\t\t\tface[9] = i/9;\r\n\t\t\tthis.addToNode(face,root,0);\r\n\t\t}\r\n\t}\r\n\r\n\treturn root;\r\n}\r\n\r\nOctree.prototype.addToNode = function( face, node, depth )\r\n{\r\n\tnode.inside += 1;\r\n\r\n\t//has children\r\n\tif(node.c)\r\n\t{\r\n\t\tvar aabb = this.computeAABB(face);\r\n\t\tvar added = false;\r\n\t\tfor(var i in node.c)\r\n\t\t{\r\n\t\t\tvar child = node.c[i];\r\n\t\t\tif (Octree.isInsideAABB(aabb,child))\r\n\t\t\t{\r\n\t\t\t\tthis.addToNode(face,child, depth+1);\r\n\t\t\t\tadded = true;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif(!added)\r\n\t\t{\r\n\t\t\tif(node.faces == null)\r\n\t\t\t\tnode.faces = [];\r\n\t\t\tnode.faces.push(face);\r\n\t\t}\r\n\t}\r\n\telse //add till full, then split\r\n\t{\r\n\t\tif(node.faces == null)\r\n\t\t\tnode.faces = [];\r\n\t\tnode.faces.push(face);\r\n\r\n\t\t//split\r\n\t\tif(node.faces.length > this.max_node_triangles && depth < Octree.MAX_OCTREE_DEPTH)\r\n\t\t{\r\n\t\t\tthis.splitNode(node);\r\n\t\t\tif(this.total_depth < depth + 1)\r\n\t\t\t\tthis.total_depth = depth + 1;\r\n\r\n\t\t\tvar faces = node.faces.concat();\r\n\t\t\tnode.faces = null;\r\n\r\n\t\t\t//redistribute all nodes\r\n\t\t\tfor(var i in faces)\r\n\t\t\t{\r\n\t\t\t\tvar face = faces[i];\r\n\t\t\t\tvar aabb = this.computeAABB(face);\r\n\t\t\t\tvar added = false;\r\n\t\t\t\tfor(var j in node.c)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar child = node.c[j];\r\n\t\t\t\t\tif (Octree.isInsideAABB(aabb,child))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.addToNode(face,child, depth+1);\r\n\t\t\t\t\t\tadded = true;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (!added)\r\n\t\t\t\t{\r\n\t\t\t\t\tif(node.faces == null)\r\n\t\t\t\t\t\tnode.faces = [];\r\n\t\t\t\t\tnode.faces.push(face);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\nOctree.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]];\r\n\r\nOctree.prototype.splitNode = function(node)\r\n{\r\n\tnode.c = [];\r\n\tvar 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];\r\n\r\n\tfor(var i in this.octree_pos_ref)\r\n\t{\r\n\t\tvar ref = this.octree_pos_ref[i];\r\n\r\n\t\tvar newnode = {};\r\n\t\tthis.total_nodes += 1;\r\n\r\n\t\tnewnode.min = [ node.min[0] + half[0] * ref[0],  node.min[1] + half[1] * ref[1],  node.min[2] + half[2] * ref[2]];\r\n\t\tnewnode.max = [newnode.min[0] + half[0], newnode.min[1] + half[1], newnode.min[2] + half[2]];\r\n\t\tnewnode.faces = null;\r\n\t\tnewnode.inside = 0;\r\n\t\tnode.c.push(newnode);\r\n\t}\r\n}\r\n\r\nOctree.prototype.computeAABB = function(vertices)\r\n{\r\n\tvar min = new Float32Array([ vertices[0], vertices[1], vertices[2] ]);\r\n\tvar max = new Float32Array([ vertices[0], vertices[1], vertices[2] ]);\r\n\r\n\tfor(var i = 0; i < vertices.length; i+=3)\r\n\t{\r\n\t\tfor(var j = 0; j < 3; j++)\r\n\t\t{\r\n\t\t\tif(min[j] > vertices[i+j]) \r\n\t\t\t\tmin[j] = vertices[i+j];\r\n\t\t\tif(max[j] < vertices[i+j]) \r\n\t\t\t\tmax[j] = vertices[i+j];\r\n\t\t}\r\n\t}\r\n\r\n\treturn {min: min, max: max, size: vec3.sub( vec3.create(), max, min) };\r\n}\r\n\r\n//remove empty nodes\r\nOctree.prototype.trim = function(node)\r\n{\r\n\tnode = node || this.root;\r\n\tif(!node.c)\r\n\t\treturn 1;\r\n\r\n\tvar num = 1;\r\n\tvar valid = [];\r\n\tvar c = node.c;\r\n\tfor(var i = 0; i < c.length; ++i)\r\n\t{\r\n\t\tif(c[i].inside)\r\n\t\t{\r\n\t\t\tvalid.push(c[i]);\r\n\t\t\tnum += this.trim(c[i]);\r\n\t\t}\r\n\t}\r\n\tnode.c = valid;\r\n\treturn num;\r\n}\r\n\r\n/**\r\n* Test collision between ray and triangles in the octree\r\n* @method testRay\r\n* @param {vec3} origin ray origin position\r\n* @param {vec3} direction ray direction position\r\n* @param {number} dist_min\r\n* @param {number} dist_max\r\n* @return {HitTest} object containing pos and normal\r\n*/\r\nOctree.prototype.testRay = (function(){ \r\n\tvar origin_temp = vec3.create();\r\n\tvar direction_temp = vec3.create();\r\n\tvar min_temp = vec3.create();\r\n\tvar max_temp = vec3.create();\r\n\r\n\treturn function(origin, direction, dist_min, dist_max, test_backfaces )\r\n\t{\r\n\t\toctree_tested_boxes = 0;\r\n\t\toctree_tested_triangles = 0;\r\n\r\n\t\tif(!this.root)\r\n\t\t{\r\n\t\t\tthrow(\"Error: octree not build\");\r\n\t\t}\r\n\r\n\t\torigin_temp.set( origin );\r\n\t\tdirection_temp.set( direction );\r\n\t\tmin_temp.set( this.root.min );\r\n\t\tmax_temp.set( this.root.max );\r\n\r\n\t\tvar test = Octree.hitTestBox( origin_temp, direction_temp, min_temp, max_temp );\r\n\t\tif(!test) //no collision with mesh bounding box\r\n\t\t\treturn null;\r\n\r\n\t\tvar test = Octree.testRayInNode( this.root, origin_temp, direction_temp, test_backfaces );\r\n\t\tif(test != null)\r\n\t\t{\r\n\t\t\tvar pos = vec3.scale( vec3.create(), direction, test.t );\r\n\t\t\tvec3.add( pos, pos, origin );\r\n\t\t\ttest.pos = pos;\r\n\t\t\treturn test;\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n})();\r\n\r\n/**\r\n* test collision between sphere and the triangles in the octree (only test if there is any vertex inside the sphere)\r\n* @method testSphere\r\n* @param {vec3} origin sphere center\r\n* @param {number} radius\r\n* @return {Boolean} true if the sphere collided with the mesh\r\n*/\r\nOctree.prototype.testSphere = function( origin, radius )\r\n{\r\n\torigin = vec3.clone(origin);\r\n\toctree_tested_boxes = 0;\r\n\toctree_tested_triangles = 0;\r\n\r\n\tif(!this.root)\r\n\t\tthrow(\"Error: octree not build\");\r\n\r\n\t//better to use always the radius squared, because all the calculations are going to do that\r\n\tvar rr = radius * radius;\r\n\r\n\tif( !Octree.testSphereBox( origin, rr, vec3.clone(this.root.min), vec3.clone(this.root.max) ) )\r\n\t\treturn false; //out of the box\r\n\r\n\treturn Octree.testSphereInNode( this.root, origin, rr );\r\n}\r\n\r\n//WARNING: cannot use static here, it uses recursion\r\nOctree.testRayInNode = function( node, origin, direction, test_backfaces )\r\n{\r\n\tvar test = null;\r\n\tvar prev_test = null;\r\n\toctree_tested_boxes += 1;\r\n\r\n\t//test faces\r\n\tif(node.faces)\r\n\t\tfor(var i = 0, l = node.faces.length; i < l; ++i)\r\n\t\t{\r\n\t\t\tvar face = node.faces[i];\r\n\t\t\toctree_tested_triangles += 1;\r\n\t\t\ttest = Octree.hitTestTriangle( origin, direction, face.subarray(0,3) , face.subarray(3,6), face.subarray(6,9), test_backfaces );\r\n\t\t\tif (test==null)\r\n\t\t\t\tcontinue;\r\n\t\t\ttest.face = face;\r\n\t\t\tif(prev_test)\r\n\t\t\t\tprev_test.mergeWith( test );\r\n\t\t\telse\r\n\t\t\t\tprev_test = test;\r\n\t\t}\r\n\r\n\t//WARNING: cannot use statics here, this function uses recursion\r\n\tvar child_min = vec3.create();\r\n\tvar child_max = vec3.create();\r\n\r\n\t//test children nodes faces\r\n\tvar child;\r\n\tif(node.c)\r\n\t\tfor(var i = 0; i < node.c.length; ++i)\r\n\t\t{\r\n\t\t\tchild = node.c[i];\r\n\t\t\tchild_min.set( child.min );\r\n\t\t\tchild_max.set( child.max );\r\n\r\n\t\t\t//test with node box\r\n\t\t\ttest = Octree.hitTestBox( origin, direction, child_min, child_max );\r\n\t\t\tif( test == null )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\t//nodebox behind current collision, then ignore node\r\n\t\t\tif(prev_test && test.t > prev_test.t)\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\t//test collision with node\r\n\t\t\ttest = Octree.testRayInNode( child, origin, direction, test_backfaces );\r\n\t\t\tif(test == null)\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tif(prev_test)\r\n\t\t\t\tprev_test.mergeWith( test );\r\n\t\t\telse\r\n\t\t\t\tprev_test = test;\r\n\t\t}\r\n\r\n\treturn prev_test;\r\n}\r\n\r\n//WARNING: cannot use static here, it uses recursion\r\nOctree.testSphereInNode = function( node, origin, radius2 )\r\n{\r\n\tvar test = null;\r\n\tvar prev_test = null;\r\n\toctree_tested_boxes += 1;\r\n\r\n\t//test faces\r\n\tif(node.faces)\r\n\t\tfor(var i = 0, l = node.faces.length; i < l; ++i)\r\n\t\t{\r\n\t\t\tvar face = node.faces[i];\r\n\t\t\toctree_tested_triangles += 1;\r\n\t\t\tif( Octree.testSphereTriangle( origin, radius2, face.subarray(0,3) , face.subarray(3,6), face.subarray(6,9) ) )\r\n\t\t\t\treturn true;\r\n\t\t}\r\n\r\n\t//WARNING: cannot use statics here, this function uses recursion\r\n\tvar child_min = vec3.create();\r\n\tvar child_max = vec3.create();\r\n\r\n\t//test children nodes faces\r\n\tvar child;\r\n\tif(node.c)\r\n\t\tfor(var i = 0; i < node.c.length; ++i)\r\n\t\t{\r\n\t\t\tchild = node.c[i];\r\n\t\t\tchild_min.set( child.min );\r\n\t\t\tchild_max.set( child.max );\r\n\r\n\t\t\t//test with node box\r\n\t\t\tif( !Octree.testSphereBox( origin, radius2, child_min, child_max ) )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\t//test collision with node content\r\n\t\t\tif( Octree.testSphereInNode( child, origin, radius2 ) )\r\n\t\t\t\treturn true;\r\n\t\t}\r\n\r\n\treturn false;\r\n}\r\n\r\n//test if one bounding is inside or overlapping another bounding\r\nOctree.isInsideAABB = function(a,b)\r\n{\r\n\tif(a.min[0] < b.min[0] || a.min[1] < b.min[1] || a.min[2] < b.min[2] ||\r\n\t\ta.max[0] > b.max[0] || a.max[1] > b.max[1] || a.max[2] > b.max[2])\r\n\t\treturn false;\r\n\treturn true;\r\n}\r\n\r\n\r\nOctree.hitTestBox = (function(){ \r\n\tvar tMin = vec3.create();\r\n\tvar tMax = vec3.create();\r\n\tvar inv = vec3.create();\r\n\tvar t1 = vec3.create();\r\n\tvar t2 = vec3.create();\r\n\tvar tmp = vec3.create();\r\n\tvar epsilon = 1.0e-6;\r\n\tvar eps = vec3.fromValues( epsilon,epsilon,epsilon );\r\n\t\r\n\treturn function( origin, ray, box_min, box_max ) {\r\n\t\tvec3.subtract( tMin, box_min, origin );\r\n\t\tvec3.subtract( tMax, box_max, origin );\r\n\t\t\r\n\t\tif(\tvec3.maxValue(tMin) < 0 && vec3.minValue(tMax) > 0)\r\n\t\t\treturn new HitTest(0,origin,ray);\r\n\r\n\t\tinv[0] = 1/ray[0];\tinv[1] = 1/ray[1];\tinv[2] = 1/ray[2];\r\n\t\tvec3.multiply(tMin, tMin, inv);\r\n\t\tvec3.multiply(tMax, tMax, inv);\r\n\t\tvec3.min(t1, tMin, tMax);\r\n\t\tvec3.max(t2, tMin, tMax);\r\n\t\tvar tNear = vec3.maxValue(t1);\r\n\t\tvar tFar = vec3.minValue(t2);\r\n\r\n\t\tif (tNear > 0 && tNear < tFar) {\r\n\t\t\tvar hit = vec3.add( vec3.create(), vec3.scale(tmp, ray, tNear ), origin);\r\n\t\t\tvec3.add( box_min, box_min, eps);\r\n\t\t\tvec3.subtract(box_min, box_min, eps);\r\n\t\t\treturn new HitTest(tNear, hit, vec3.fromValues(\r\n\t\t\t  (hit[0] > box_max[0]) - (hit[0] < box_min[0]),\r\n\t\t\t  (hit[1] > box_max[1]) - (hit[1] < box_min[1]),\r\n\t\t\t  (hit[2] > box_max[2]) - (hit[2] < box_min[2]) ));\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n})();\r\n\r\nOctree.hitTestTriangle = (function(){ \r\n\t\r\n\tvar AB = vec3.create();\r\n\tvar AC = vec3.create();\r\n\tvar toHit = vec3.create();\r\n\tvar tmp = vec3.create();\r\n\t\r\n\treturn function( origin, ray, A, B, C, test_backfaces ) {\r\n\t\tvec3.subtract( AB, B, A );\r\n\t\tvec3.subtract( AC, C, A );\r\n\t\tvar normal = vec3.cross( vec3.create(), AB, AC ); //returned\r\n\t\tvec3.normalize( normal, normal );\r\n\t\tif( !test_backfaces && vec3.dot(normal,ray) > 0)\r\n\t\t\treturn null; //ignore backface\r\n\r\n\t\tvar t = vec3.dot(normal, vec3.subtract( tmp, A, origin )) / vec3.dot(normal,ray);\r\n\r\n\t    if (t > 0)\r\n\t\t{\r\n\t\t\tvar hit = vec3.scale(vec3.create(), ray, t); //returned\r\n\t\t\tvec3.add(hit, hit, origin);\r\n\t\t\tvec3.subtract( toHit, hit, A );\r\n\t\t\tvar dot00 = vec3.dot(AC,AC);\r\n\t\t\tvar dot01 = vec3.dot(AC,AB);\r\n\t\t\tvar dot02 = vec3.dot(AC,toHit);\r\n\t\t\tvar dot11 = vec3.dot(AB,AB);\r\n\t\t\tvar dot12 = vec3.dot(AB,toHit);\r\n\t\t\tvar divide = dot00 * dot11 - dot01 * dot01;\r\n\t\t\tvar u = (dot11 * dot02 - dot01 * dot12) / divide;\r\n\t\t\tvar v = (dot00 * dot12 - dot01 * dot02) / divide;\r\n\t\t\tif (u >= 0 && v >= 0 && u + v <= 1)\r\n\t\t\t\treturn new HitTest(t, hit, normal);\r\n\t\t}\r\n\t    return null;\r\n\t};\r\n})();\r\n\r\n//from http://realtimecollisiondetection.net/blog/?p=103\r\n//radius must be squared\r\nOctree.testSphereTriangle = (function(){ \r\n\t\r\n\tvar A = vec3.create();\r\n\tvar B = vec3.create();\r\n\tvar C = vec3.create();\r\n\tvar AB = vec3.create();\r\n\tvar AC = vec3.create();\r\n\tvar BC = vec3.create();\r\n\tvar CA = vec3.create();\r\n\tvar V = vec3.create();\r\n\t\r\n\treturn function( P, rr, A_, B_, C_ ) {\r\n\t\tvec3.sub( A, A_, P );\r\n\t\tvec3.sub( B, B_, P );\r\n\t\tvec3.sub( C, C_, P );\r\n\r\n\t\tvec3.sub( AB, B, A );\r\n\t\tvec3.sub( AC, C, A );\r\n\r\n\t\tvec3.cross( V, AB, AC );\r\n\t\tvar d = vec3.dot( A, V );\r\n\t\tvar e = vec3.dot( V, V );\r\n\t\tvar sep1 = d * d > rr * e;\r\n\t\tvar aa = vec3.dot(A, A);\r\n\t\tvar ab = vec3.dot(A, B);\r\n\t\tvar ac = vec3.dot(A, C);\r\n\t\tvar bb = vec3.dot(B, B);\r\n\t\tvar bc = vec3.dot(B, C);\r\n\t\tvar cc = vec3.dot(C, C);\r\n\t\tvar sep2 = (aa > rr) & (ab > aa) & (ac > aa);\r\n\t\tvar sep3 = (bb > rr) & (ab > bb) & (bc > bb);\r\n\t\tvar sep4 = (cc > rr) & (ac > cc) & (bc > cc);\r\n\r\n\t\tvar d1 = ab - aa;\r\n\t\tvar d2 = bc - bb;\r\n\t\tvar d3 = ac - cc;\r\n\r\n\t\tvec3.sub( BC, C, B );\r\n\t\tvec3.sub( CA, A, C );\r\n\r\n\t\tvar e1 = vec3.dot(AB, AB);\r\n\t\tvar e2 = vec3.dot(BC, BC);\r\n\t\tvar e3 = vec3.dot(CA, CA);\r\n\r\n\t\tvar Q1 = vec3.scale(vec3.create(), A, e1); vec3.sub( Q1, Q1, vec3.scale(vec3.create(), AB, d1) );\r\n\t\tvar Q2 = vec3.scale(vec3.create(), B, e2); vec3.sub( Q2, Q2, vec3.scale(vec3.create(), BC, d2) );\r\n\t\tvar Q3 = vec3.scale(vec3.create(), C, e3); vec3.sub( Q3, Q3, vec3.scale(vec3.create(), CA, d3) );\r\n\r\n\t\tvar QC = vec3.scale( vec3.create(), C, e1 ); QC = vec3.sub( QC, QC, Q1 );\r\n\t\tvar QA = vec3.scale( vec3.create(), A, e2 ); QA = vec3.sub( QA, QA, Q2 );\r\n\t\tvar QB = vec3.scale( vec3.create(), B, e3 ); QB = vec3.sub( QB, QB, Q3 );\r\n\r\n\t\tvar sep5 = ( vec3.dot(Q1, Q1) > rr * e1 * e1) & (vec3.dot(Q1, QC) > 0 );\r\n\t\tvar sep6 = ( vec3.dot(Q2, Q2) > rr * e2 * e2) & (vec3.dot(Q2, QA) > 0 );\r\n\t\tvar sep7 = ( vec3.dot(Q3, Q3) > rr * e3 * e3) & (vec3.dot(Q3, QB) > 0 );\r\n\r\n\t\tvar separated = sep1 | sep2 | sep3 | sep4 | sep5 | sep6 | sep7\r\n\t\treturn !separated;\r\n\t};\r\n})();\r\n\r\nOctree.testSphereBox = function( center, radius2, box_min, box_max ) {\r\n\r\n\t// arvo's algorithm from gamasutra\r\n\t// http://www.gamasutra.com/features/19991018/Gomez_4.htm\r\n\tvar s, d = 0.0;\r\n\t//find the square of the distance\r\n\t//from the sphere to the box\r\n\tfor(var i = 0; i < 3; ++i) \r\n\t{ \r\n\t\tif( center[i] < box_min[i] )\r\n\t\t{\r\n\t\t\ts = center[i] - box_min[i];\r\n\t\t\td += s*s; \r\n\t\t}\r\n\t\telse if( center[i] > box_max[i] )\r\n\t\t{ \r\n\t\t\ts = center[i] - box_max[i];\r\n\t\t\td += s*s; \r\n\t\t}\r\n\t}\r\n\t//return d <= r*r\r\n\r\n\tif (d <= radius2)\r\n\t{\r\n\t\treturn true;\r\n\t\t/*\r\n\t\t// this is used just to know if it overlaps or is just inside, but I dont care\r\n\t\t// make an aabb aabb test with the sphere aabb to test inside state\r\n\t\tvar halfsize = vec3.fromValues( radius, radius, radius );\r\n\t\tvar sphere_bbox = BBox.fromCenterHalfsize( center, halfsize );\r\n\t\tif ( geo.testBBoxBBox(bbox, sphere_bbox) )\r\n\t\t\treturn INSIDE;\r\n\t\treturn OVERLAP;\t\r\n\t\t*/\r\n\t}\r\n\r\n\treturn false; //OUTSIDE;\r\n};\n// Provides a convenient raytracing interface.\r\n\r\n// ### new GL.HitTest([t, hit, normal])\r\n// \r\n// This is the object used to return hit test results. If there are no\r\n// arguments, the constructed argument represents a hit infinitely far\r\n// away.\r\nglobal.HitTest = GL.HitTest = function HitTest(t, hit, normal) {\r\n  this.t = arguments.length ? t : Number.MAX_VALUE;\r\n  this.hit = hit;\r\n  this.normal = normal;\r\n  this.face = null;\r\n}\r\n\r\n// ### .mergeWith(other)\r\n// \r\n// Changes this object to be the closer of the two hit test results.\r\nHitTest.prototype = {\r\n  mergeWith: function(other) {\r\n    if (other.t > 0 && other.t < this.t) {\r\n      this.t = other.t;\r\n      this.hit = other.hit;\r\n      this.normal = other.normal;\r\n\t  this.face = other.face;\r\n    }\r\n  }\r\n};\r\n\r\n// ### new GL.Ray( origin, direction )\r\nglobal.Ray = GL.Ray = function Ray( origin, direction )\r\n{\r\n\tthis.origin = vec3.create();\r\n\tthis.direction = vec3.create();\r\n\tthis.collision_point = vec3.create();\r\n\r\n\tif(origin)\r\n\t\tthis.origin.set( origin );\r\n\tif(direction)\r\n\t\tthis.direction.set( direction );\r\n}\r\n\r\nRay.prototype.testPlane = function( P, N )\r\n{\r\n\treturn geo.testRayPlane( this.origin, this.direction, P, N, this.collision_point );\r\n}\r\n\r\nRay.prototype.testSphere = function( center, radius, max_dist )\r\n{\r\n\treturn geo.testRaySphere( this.origin, this.direction, center, radius, this.collision_point, max_dist );\r\n}\r\n\r\n// ### new GL.Raytracer()\r\n// \r\n// This will read the current modelview matrix, projection matrix, and viewport,\r\n// reconstruct the eye position, and store enough information to later generate\r\n// per-pixel rays using `getRayForPixel()`.\r\n// \r\n// Example usage:\r\n// \r\n//     var tracer = new GL.Raytracer();\r\n//     var ray = tracer.getRayForPixel(\r\n//       gl.canvas.width / 2,\r\n//       gl.canvas.height / 2);\r\n//       var result = GL.Raytracer.hitTestSphere(\r\n//       tracer.eye, ray, new GL.Vector(0, 0, 0), 1);\r\n\r\nglobal.Raytracer = GL.Raytracer = function Raytracer( viewprojection_matrix, viewport ) {\r\n\tthis.viewport = vec4.create();\r\n\tthis.ray00 = vec3.create();\r\n\tthis.ray10 = vec3.create();\r\n\tthis.ray01 = vec3.create();\r\n\tthis.ray11 = vec3.create();\r\n\tthis.eye = vec3.create();\r\n\tthis.setup( viewprojection_matrix, viewport );\r\n}\r\n\r\nRaytracer.prototype.setup = function( viewprojection_matrix, viewport )\r\n{\r\n\tviewport = viewport || gl.viewport_data;\r\n\tthis.viewport.set( viewport );\r\n\r\n\tvar minX = viewport[0], maxX = minX + viewport[2];\r\n\tvar minY = viewport[1], maxY = minY + viewport[3];\r\n\r\n\tvec3.set( this.ray00, minX, minY, 1 );\r\n\tvec3.set( this.ray10, maxX, minY, 1 );\r\n\tvec3.set( this.ray01, minX, maxY, 1 );\r\n\tvec3.set( this.ray11, maxX, maxY, 1 );\r\n\tvec3.unproject( this.ray00, this.ray00, viewprojection_matrix, viewport);\r\n\tvec3.unproject( this.ray10, this.ray10, viewprojection_matrix, viewport);\r\n\tvec3.unproject( this.ray01, this.ray01, viewprojection_matrix, viewport);\r\n\tvec3.unproject( this.ray11, this.ray11, viewprojection_matrix, viewport);\r\n\tvar eye = this.eye;\r\n\tvec3.unproject(eye, eye, viewprojection_matrix, viewport);\r\n\tvec3.subtract(this.ray00, this.ray00, eye);\r\n\tvec3.subtract(this.ray10, this.ray10, eye);\r\n\tvec3.subtract(this.ray01, this.ray01, eye);\r\n\tvec3.subtract(this.ray11, this.ray11, eye);\r\n}\r\n\r\n  // ### .getRayForPixel(x, y)\r\n  // \r\n  // Returns the ray direction originating from the camera and traveling through the pixel `x, y`.\r\nRaytracer.prototype.getRayForPixel = (function(){ \r\n\tvar ray0 = vec3.create();\r\n\tvar ray1 = vec3.create();\r\n\treturn function(x, y, out) {\r\n\t\tout = out || vec3.create();\r\n\t\tx = (x - this.viewport[0]) / this.viewport[2];\r\n\t\ty = 1 - (y - this.viewport[1]) / this.viewport[3];\r\n\t\tvec3.lerp(ray0, this.ray00, this.ray10, x);\r\n\t\tvec3.lerp(ray1, this.ray01, this.ray11, x);\r\n\t\tvec3.lerp( out, ray0, ray1, y)\r\n\t\treturn vec3.normalize( out, out );\r\n\t}\r\n})();\r\n\r\n// ### GL.Raytracer.hitTestBox(origin, ray, min, max)\r\n// \r\n// Traces the ray starting from `origin` along `ray` against the axis-aligned box\r\n// whose coordinates extend from `min` to `max`. Returns a `HitTest` with the\r\n// information or `null` for no intersection.\r\n// \r\n// This implementation uses the [slab intersection method](http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter3.htm).\r\nvar _hittest_inv = mat4.create();\r\nRaytracer.hitTestBox = function(origin, ray, min, max, model) {\r\n  var _hittest_v3 = new Float32Array(10*3); //reuse memory to speedup\r\n  \r\n  if(model)\r\n  {\r\n\tvar inv = mat4.invert( _hittest_inv, model );\r\n\torigin = mat4.multiplyVec3( _hittest_v3.subarray(3,6), inv, origin );\r\n\tray = mat4.rotateVec3( _hittest_v3.subarray(6,9), inv, ray );\r\n  }\r\n\r\n  var tMin = vec3.subtract( _hittest_v3.subarray(9,12), min, origin );\r\n  vec3.divide( tMin, tMin, ray );\r\n\r\n  var tMax = vec3.subtract( _hittest_v3.subarray(12,15), max, origin );\r\n  vec3.divide( tMax, tMax, ray );\r\n\r\n  var t1 = vec3.min( _hittest_v3.subarray(15,18), tMin, tMax);\r\n  var t2 = vec3.max( _hittest_v3.subarray(18,21), tMin, tMax);\r\n\r\n  var tNear = vec3.maxValue(t1);\r\n  var tFar = vec3.minValue(t2);\r\n\r\n  if (tNear > 0 && tNear <= tFar) {\r\n    var epsilon = 1.0e-6;\r\n\tvar hit = vec3.scale( _hittest_v3.subarray(21,24), ray, tNear);\r\n\tvec3.add( hit, origin, hit );\r\n\r\n    vec3.addValue(_hittest_v3.subarray(24,27), min, epsilon);\r\n    vec3.subValue(_hittest_v3.subarray(27,30), max, epsilon);\r\n\r\n    return new HitTest(tNear, hit, vec3.fromValues(\r\n      (hit[0] > max[0]) - (hit[0] < min[0]),\r\n      (hit[1] > max[1]) - (hit[1] < min[1]),\r\n      (hit[2] > max[2]) - (hit[2] < min[2])\r\n    ));\r\n  }\r\n\r\n  return null;\r\n};\r\n\r\n\r\n\r\n\r\n// ### GL.Raytracer.hitTestSphere(origin, ray, center, radius)\r\n// \r\n// Traces the ray starting from `origin` along `ray` against the sphere defined\r\n// by `center` and `radius`. Returns a `HitTest` with the information or `null`\r\n// for no intersection.\r\nRaytracer.hitTestSphere = function(origin, ray, center, radius) {\r\n  var offset = vec3.subtract( vec3.create(), origin,center);\r\n  var a = vec3.dot(ray,ray);\r\n  var b = 2 * vec3.dot(ray,offset);\r\n  var c = vec3.dot(offset,offset) - radius * radius;\r\n  var discriminant = b * b - 4 * a * c;\r\n\r\n  if (discriminant > 0) {\r\n    var t = (-b - Math.sqrt(discriminant)) / (2 * a), hit = vec3.add(vec3.create(),origin, vec3.scale(vec3.create(), ray, t));\r\n    return new HitTest(t, hit, vec3.scale( vec3.create(), vec3.subtract(vec3.create(), hit,center), 1.0/radius));\r\n  }\r\n\r\n  return null;\r\n};\r\n\r\n\r\n// ### GL.Raytracer.hitTestTriangle(origin, ray, a, b, c)\r\n// \r\n// Traces the ray starting from `origin` along `ray` against the triangle defined\r\n// by the points `a`, `b`, and `c`. Returns a `HitTest` with the information or\r\n// `null` for no intersection.\r\nRaytracer.hitTestTriangle = function(origin, ray, a, b, c) {\r\n  var ab = vec3.subtract(vec3.create(), b,a );\r\n  var ac = vec3.subtract(vec3.create(), c,a );\r\n  var normal = vec3.cross( vec3.create(), ab,ac);\r\n  vec3.normalize( normal, normal );\r\n  var t = vec3.dot(normal, vec3.subtract( vec3.create(), a,origin)) / vec3.dot(normal,ray);\r\n\r\n  if (t > 0) {\r\n    var hit = vec3.add( vec3.create(), origin, vec3.scale(vec3.create(), ray,t));\r\n    var toHit = vec3.subtract( vec3.create(), hit, a);\r\n    var dot00 = vec3.dot(ac,ac);\r\n    var dot01 = vec3.dot(ac,ab);\r\n    var dot02 = vec3.dot(ac,toHit);\r\n    var dot11 = vec3.dot(ab,ab);\r\n    var dot12 = vec3.dot(ab,toHit);\r\n    var divide = dot00 * dot11 - dot01 * dot01;\r\n    var u = (dot11 * dot02 - dot01 * dot12) / divide;\r\n    var v = (dot00 * dot12 - dot01 * dot02) / divide;\r\n    if (u >= 0 && v >= 0 && u + v <= 1) return new HitTest(t, hit, normal);\r\n  }\r\n\r\n  return null;\r\n};\n//***** OBJ parser adapted from SpiderGL implementation *****************\r\n/**\r\n* Parses a OBJ string and returns an object with the info ready to be passed to GL.Mesh.load\r\n* @method Mesh.parseOBJ\r\n* @param {String} data all the OBJ info to be parsed\r\n* @param {Object} options\r\n* @return {Object} mesh information (vertices, coords, normals, indices)\r\n*/\r\n\r\nMesh.parseOBJ = function( text, options )\r\n{\r\n\toptions = options || {};\r\n\r\n\t//final arrays (packed, lineal [ax,ay,az, bx,by,bz ...])\r\n\tvar positionsArray = [ ];\r\n\tvar texcoordsArray = [ ];\r\n\tvar normalsArray   = [ ];\r\n\tvar indicesArray   = [ ];\r\n\r\n\t//unique arrays (not packed, lineal)\r\n\tvar positions = [ ];\r\n\tvar texcoords = [ ];\r\n\tvar normals   = [ ];\r\n\tvar facemap   = { };\r\n\tvar index     = 0;\r\n\r\n\tvar line = null;\r\n\tvar f   = null;\r\n\tvar pos = 0;\r\n\tvar tex = 0;\r\n\tvar nor = 0;\r\n\tvar x   = 0.0;\r\n\tvar y   = 0.0;\r\n\tvar z   = 0.0;\r\n\tvar tokens = null;\r\n\r\n\tvar hasPos = false;\r\n\tvar hasTex = false;\r\n\tvar hasNor = false;\r\n\r\n\tvar parsingFaces = false;\r\n\tvar indices_offset = 0;\r\n\tvar negative_offset = -1; //used for weird objs with negative indices\r\n\tvar max_index = 0;\r\n\r\n\tvar skip_indices = options.noindex ? options.noindex : (text.length > 10000000 ? true : false);\r\n\t//trace(\"SKIP INDICES: \" + skip_indices);\r\n\tvar flip_axis = options.flipAxis;\r\n\tvar flip_normals = (flip_axis || options.flipNormals);\r\n\r\n\t//used for mesh groups (submeshes)\r\n\tvar group = null;\r\n\tvar groups = [];\r\n\tvar materials_found = {};\r\n\r\n\tvar V_CODE = 1;\r\n\tvar VT_CODE = 2;\r\n\tvar VN_CODE = 3;\r\n\tvar F_CODE = 4;\r\n\tvar G_CODE = 5;\r\n\tvar O_CODE = 6;\r\n\tvar codes = { v: V_CODE, vt: VT_CODE, vn: VN_CODE, f: F_CODE, g: G_CODE, o: O_CODE };\r\n\r\n\r\n\tvar lines = text.split(\"\\n\");\r\n\tvar length = lines.length;\r\n\tfor (var lineIndex = 0;  lineIndex < length; ++lineIndex) {\r\n\t\tline = lines[lineIndex].replace(/[ \\t]+/g, \" \").replace(/\\s\\s*$/, \"\"); //trim\r\n\r\n\t\tif (line[0] == \"#\") continue;\r\n\t\tif(line == \"\") continue;\r\n\r\n\t\ttokens = line.split(\" \");\r\n\t\tvar code = codes[ tokens[0] ];\r\n\r\n\t\tif(parsingFaces && code == V_CODE) //another mesh?\r\n\t\t{\r\n\t\t\tindices_offset = index;\r\n\t\t\tparsingFaces = false;\r\n\t\t\t//trace(\"multiple meshes: \" + indices_offset);\r\n\t\t}\r\n\r\n\t\t//read and parse numbers\r\n\t\tif( code <= VN_CODE ) //v,vt,vn\r\n\t\t{\r\n\t\t\tx = parseFloat(tokens[1]);\r\n\t\t\ty = parseFloat(tokens[2]);\r\n\t\t\tif( code != VT_CODE )\r\n\t\t\t{\r\n\t\t\t\tif(tokens[3] == '\\\\') //super weird case, OBJ allows to break lines with slashes...\r\n\t\t\t\t{\r\n\t\t\t\t\t//HACK! only works if the var is the thirth position...\r\n\t\t\t\t\t++lineIndex;\r\n\t\t\t\t\tline = lines[lineIndex].replace(/[ \\t]+/g, \" \").replace(/\\s\\s*$/, \"\"); //better than trim\r\n\t\t\t\t\tz = parseFloat(line);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t\tz = parseFloat(tokens[3]);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (code == V_CODE) {\r\n\t\t\tif(flip_axis) //maya and max notation style\r\n\t\t\t\tpositions.push(-1*x,z,y);\r\n\t\t\telse\r\n\t\t\t\tpositions.push(x,y,z);\r\n\t\t}\r\n\t\telse if (code == VT_CODE) {\r\n\t\t\ttexcoords.push(x,y);\r\n\t\t}\r\n\t\telse if (code == VN_CODE) {\r\n\r\n\t\t\tif(flip_normals)  //maya and max notation style\r\n\t\t\t\tnormals.push(-y,-z,x);\r\n\t\t\telse\r\n\t\t\t\tnormals.push(x,y,z);\r\n\t\t}\r\n\t\telse if (code == F_CODE) {\r\n\t\t\tparsingFaces = true;\r\n\r\n\t\t\tif (tokens.length < 4) continue; //faces with less that 3 vertices? nevermind\r\n\r\n\t\t\t//for every corner of this polygon\r\n\t\t\tvar polygon_indices = [];\r\n\t\t\tfor (var i=1; i < tokens.length; ++i) \r\n\t\t\t{\r\n\t\t\t\tif (!(tokens[i] in facemap) || skip_indices) \r\n\t\t\t\t{\r\n\t\t\t\t\tf = tokens[i].split(\"/\");\r\n\r\n\t\t\t\t\tif (f.length == 1) { //unpacked\r\n\t\t\t\t\t\tpos = parseInt(f[0]) - 1;\r\n\t\t\t\t\t\ttex = pos;\r\n\t\t\t\t\t\tnor = pos;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (f.length == 2) { //no normals\r\n\t\t\t\t\t\tpos = parseInt(f[0]) - 1;\r\n\t\t\t\t\t\ttex = parseInt(f[1]) - 1;\r\n\t\t\t\t\t\tnor = -1;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (f.length == 3) { //all three indexed\r\n\t\t\t\t\t\tpos = parseInt(f[0]) - 1;\r\n\t\t\t\t\t\ttex = parseInt(f[1]) - 1;\r\n\t\t\t\t\t\tnor = parseInt(f[2]) - 1;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tconsole.err(\"Problem parsing: unknown number of values per face\");\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif(i > 3 && skip_indices) //break polygon in triangles\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t//first\r\n\t\t\t\t\t\tvar pl = positionsArray.length;\r\n\t\t\t\t\t\tpositionsArray.push( positionsArray[pl - (i-3)*9], positionsArray[pl - (i-3)*9 + 1], positionsArray[pl - (i-3)*9 + 2]);\r\n\t\t\t\t\t\tpositionsArray.push( positionsArray[pl - 3], positionsArray[pl - 2], positionsArray[pl - 1]);\r\n\t\t\t\t\t\tpl = texcoordsArray.length;\r\n\t\t\t\t\t\ttexcoordsArray.push( texcoordsArray[pl - (i-3)*6], texcoordsArray[pl - (i-3)*6 + 1]);\r\n\t\t\t\t\t\ttexcoordsArray.push( texcoordsArray[pl - 2], texcoordsArray[pl - 1]);\r\n\t\t\t\t\t\tpl = normalsArray.length;\r\n\t\t\t\t\t\tnormalsArray.push( normalsArray[pl - (i-3)*9], normalsArray[pl - (i-3)*9 + 1], normalsArray[pl - (i-3)*9 + 2]);\r\n\t\t\t\t\t\tnormalsArray.push( normalsArray[pl - 3], normalsArray[pl - 2], normalsArray[pl - 1]);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t//add new vertex\r\n\t\t\t\t\tx = 0.0;\r\n\t\t\t\t\ty = 0.0;\r\n\t\t\t\t\tz = 0.0;\r\n\t\t\t\t\tif ((pos * 3 + 2) < positions.length) {\r\n\t\t\t\t\t\thasPos = true;\r\n\t\t\t\t\t\tx = positions[pos*3+0];\r\n\t\t\t\t\t\ty = positions[pos*3+1];\r\n\t\t\t\t\t\tz = positions[pos*3+2];\r\n\t\t\t\t\t}\r\n\t\t\t\t\tpositionsArray.push(x,y,z);\r\n\r\n\t\t\t\t\t//add new texture coordinate\r\n\t\t\t\t\tx = 0.0;\r\n\t\t\t\t\ty = 0.0;\r\n\t\t\t\t\tif ((tex * 2 + 1) < texcoords.length) {\r\n\t\t\t\t\t\thasTex = true;\r\n\t\t\t\t\t\tx = texcoords[tex*2+0];\r\n\t\t\t\t\t\ty = texcoords[tex*2+1];\r\n\t\t\t\t\t}\r\n\t\t\t\t\ttexcoordsArray.push(x,y);\r\n\r\n\t\t\t\t\t//add new normal\r\n\t\t\t\t\tx = 0.0;\r\n\t\t\t\t\ty = 0.0;\r\n\t\t\t\t\tz = 1.0;\r\n\t\t\t\t\tif(nor != -1)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif ((nor * 3 + 2) < normals.length) {\r\n\t\t\t\t\t\t\thasNor = true;\r\n\t\t\t\t\t\t\tx = normals[nor*3+0];\r\n\t\t\t\t\t\t\ty = normals[nor*3+1];\r\n\t\t\t\t\t\t\tz = normals[nor*3+2];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tnormalsArray.push(x,y,z);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t//Save the string \"10/10/10\" and tells which index represents it in the arrays\r\n\t\t\t\t\tif(!skip_indices)\r\n\t\t\t\t\t\tfacemap[tokens[i]] = index++;\r\n\t\t\t\t}//end of 'if this token is new (store and index for later reuse)'\r\n\r\n\t\t\t\t//store key for this triplet\r\n\t\t\t\tif(!skip_indices)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar final_index = facemap[tokens[i]];\r\n\t\t\t\t\tpolygon_indices.push(final_index);\r\n\t\t\t\t\tif(max_index < final_index)\r\n\t\t\t\t\t\tmax_index = final_index;\r\n\t\t\t\t}\r\n\t\t\t} //end of for every token on a 'f' line\r\n\r\n\t\t\t//polygons (not just triangles)\r\n\t\t\tif(!skip_indices)\r\n\t\t\t{\r\n\t\t\t\tfor(var iP = 2; iP < polygon_indices.length; iP++)\r\n\t\t\t\t{\r\n\t\t\t\t\tindicesArray.push( polygon_indices[0], polygon_indices[iP-1], polygon_indices[iP] );\r\n\t\t\t\t\t//indicesArray.push( [polygon_indices[0], polygon_indices[iP-1], polygon_indices[iP]] );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (code == G_CODE) { //tokens[0] == \"usemtl\"\r\n\t\t\tnegative_offset = positions.length / 3 - 1;\r\n\r\n\t\t\tif(tokens.length > 1)\r\n\t\t\t{\r\n\t\t\t\tif(group != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tgroup.length = indicesArray.length - group.start;\r\n\t\t\t\t\tif(group.length > 0)\r\n\t\t\t\t\t\tgroups.push(group);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tgroup = {\r\n\t\t\t\t\tname: tokens[1],\r\n\t\t\t\t\tstart: indicesArray.length,\r\n\t\t\t\t\tlength: -1,\r\n\t\t\t\t\tmaterial: \"\"\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (tokens[0] == \"usemtl\") {\r\n\t\t\tif(group)\r\n\t\t\t\tgroup.material = tokens[1];\r\n\t\t}\r\n\t}\r\n\r\n\tif(!positions.length)\r\n\t{\r\n\t\tconsole.error(\"OBJ doesnt have vertices, maybe the file is not a OBJ\");\r\n\t\treturn null;\r\n\t}\r\n\r\n\tif(group && (indicesArray.length - group.start) > 1)\r\n\t{\r\n\t\tgroup.length = indicesArray.length - group.start;\r\n\t\tgroups.push(group);\r\n\t}\r\n\r\n\t//deindex streams\r\n\tif((max_index > 256*256 || skip_indices ) && indicesArray.length > 0)\r\n\t{\r\n\t\tconsole.log(\"Deindexing mesh...\")\r\n\t\tvar finalVertices = new Float32Array(indicesArray.length * 3);\r\n\t\tvar finalNormals = normalsArray && normalsArray.length ? new Float32Array(indicesArray.length * 3) : null;\r\n\t\tvar finalTexCoords = texcoordsArray && texcoordsArray.length ? new Float32Array(indicesArray.length * 2) : null;\r\n\t\tfor(var i = 0; i < indicesArray.length; i += 1)\r\n\t\t{\r\n\t\t\tfinalVertices.set( positionsArray.slice( indicesArray[i]*3,indicesArray[i]*3 + 3), i*3 );\r\n\t\t\tif(finalNormals)\r\n\t\t\t\tfinalNormals.set( normalsArray.slice( indicesArray[i]*3,indicesArray[i]*3 + 3 ), i*3 );\r\n\t\t\tif(finalTexCoords)\r\n\t\t\t\tfinalTexCoords.set( texcoordsArray.slice(indicesArray[i]*2,indicesArray[i]*2 + 2 ), i*2 );\r\n\t\t}\r\n\t\tpositionsArray = finalVertices;\r\n\t\tif(finalNormals)\r\n\t\t\tnormalsArray = finalNormals;\r\n\t\tif(finalTexCoords)\r\n\t\t\ttexcoordsArray = finalTexCoords;\r\n\t\tindicesArray = null;\r\n\t}\r\n\r\n\t//Create final mesh object\r\n\tvar mesh = {};\r\n\r\n\t//create typed arrays\r\n\tif (hasPos)\r\n\t\tmesh.vertices = new Float32Array(positionsArray);\r\n\tif (hasNor && normalsArray.length > 0)\r\n\t\tmesh.normals = new Float32Array(normalsArray);\r\n\tif (hasTex && texcoordsArray.length > 0)\r\n\t\tmesh.coords = new Float32Array(texcoordsArray);\r\n\tif (indicesArray && indicesArray.length > 0)\r\n\t\tmesh.triangles = new Uint16Array(indicesArray);\r\n\r\n\tvar info = {};\r\n\tif(groups.length > 1)\r\n\t\tinfo.groups = groups;\r\n\tmesh.info = info;\r\n\r\n\tif(options.only_data)\r\n\t\treturn mesh;\r\n\r\n\t//creates and returns a GL.Mesh\r\n\tvar final_mesh = null;\r\n\tfinal_mesh = Mesh.load( mesh, null, options.mesh );\r\n\tfinal_mesh.updateBoundingBox();\r\n\treturn final_mesh;\r\n}\r\n\r\nMesh.parsers[\"obj\"] = Mesh.parseOBJ;\r\n\r\nMesh.encoders[\"obj\"] = function( mesh, options )\r\n{\r\n\t//store vertices\r\n\tvar verticesBuffer = mesh.getBuffer(\"vertices\");\r\n\tif(!verticesBuffer)\r\n\t\treturn null;\r\n\r\n\tvar lines = [];\r\n\tlines.push(\"# Generated with liteGL.js by Javi Agenjo\\n\");\r\n\r\n\tvar vertices = verticesBuffer.data;\r\n\tfor (var i = 0; i < vertices.length; i+=3)\r\n\t\tlines.push(\"v \" + vertices[i].toFixed(4) + \" \" + vertices[i+1].toFixed(4) + \" \" + vertices[i+2].toFixed(4));\r\n\r\n\t//store normals\r\n\tvar normalsBuffer = mesh.getBuffer(\"normals\");\r\n\tif(normalsBuffer)\r\n\t{\r\n\t\tlines.push(\"\");\r\n\t\tvar normals = normalsBuffer.data;\r\n\t\tfor (var i = 0; i < normals.length; i+=3)\r\n\t\t\tlines.push(\"vn \" + normals[i].toFixed(4) + \" \" + normals[i+1].toFixed(4) + \" \" + normals[i+2].toFixed(4) );\r\n\t}\r\n\t\r\n\t//store uvs\r\n\tvar coordsBuffer = mesh.getBuffer(\"coords\");\r\n\tif(coordsBuffer)\r\n\t{\r\n\t\tlines.push(\"\");\r\n\t\tvar coords = coordsBuffer.data;\r\n\t\tfor (var i = 0; i < coords.length; i+=2)\r\n\t\t\tlines.push(\"vt \" + coords[i].toFixed(4) + \" \" + coords[i+1].toFixed(4) + \" \" + \" 0.0000\");\r\n\t}\r\n\r\n\tvar groups = mesh.info.groups;\r\n\r\n\r\n\t//store faces\r\n\tvar indicesBuffer = mesh.getIndexBuffer(\"triangles\");\r\n\tif(indicesBuffer)\r\n\t{\r\n\t\tvar indices = indicesBuffer.data;\r\n\t\tif(!groups || !groups.length)\r\n\t\t\tgroups = [{start:0, length: indices.length, name:\"mesh\"}];\r\n\t\tfor(var j = 0; j < groups.length; ++j)\r\n\t\t{\r\n\t\t\tvar group = groups[j];\r\n\t\t\tlines.push(\"g \" + group.name );\r\n\t\t\tlines.push(\"usemtl \" + (group.material || (\"mat_\"+j)));\r\n\t\t\tvar start = group.start;\r\n\t\t\tvar end = start + group.length;\r\n\t\t\tfor (var i = start; i < end; i+=3)\r\n\t\t\t\tlines.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) );\r\n\t\t}\r\n\t}\r\n\telse //no indices\r\n\t{\r\n\t\tif(!groups || !groups.length)\r\n\t\t\tgroups = [{start:0, length: (vertices.length / 3), name:\"mesh\"}];\r\n\t\tfor(var j = 0; j < groups.length; ++j)\r\n\t\t{\r\n\t\t\tvar group = groups[j];\r\n\t\t\tlines.push(\"g \" + group.name);\r\n\t\t\tlines.push(\"usemtl \" + (group.material || (\"mat_\"+j)));\r\n\t\t\tvar start = group.start;\r\n\t\t\tvar end = start + group.length;\r\n\t\t\tfor (var i = start; i < end; i+=3)\r\n\t\t\t\tlines.push( \"f \" + (i+1) + \"/\" + (i+1) + \"/\" + (i+1) + \" \" + (i+2) + \"/\" + (i+2) + \"/\" + (i+2) + \" \" + (i+3) + \"/\" + (i+3) + \"/\" + (i+3) );\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn lines.join(\"\\n\");\r\n}\r\n\r\n//simple format to output meshes in ASCII\r\nMesh.parsers[\"mesh\"] = function( text, options )\r\n{\r\n\tvar mesh = {};\r\n\r\n\tvar lines = text.split(\"\\n\");\r\n\tfor(var i = 0; i < lines.length; ++i)\r\n\t{\r\n\t\tvar line = lines[i];\r\n\t\tvar type = line[0];\r\n\t\tvar t = line.substr(1).split(\",\");\r\n\t\tvar name = t[0];\r\n\r\n\t\tif(type == \"-\") //buffer\r\n\t\t{\r\n\t\t\tvar data = new Float32Array( Number(t[1]) );\r\n\t\t\tfor(var j = 0; j < data.length; ++j)\r\n\t\t\t\tdata[j] = Number(t[j+2]);\r\n\t\t\tmesh[name] = data;\r\n\t\t}\r\n\t\telse if(type == \"*\") //index\r\n\t\t{\r\n\t\t\tvar data = Number(t[1]) > 256*256 ? new Uint32Array( Number(t[1]) ) : new Uint16Array( Number(t[1]) );\r\n\t\t\tfor(var j = 0; j < data.length; ++j)\r\n\t\t\t\tdata[j] = Number(t[j+2]);\r\n\t\t\tmesh[name] = data;\r\n\t\t}\r\n\t\telse if(type == \"@\") //info\r\n\t\t{\r\n\t\t\tif(name == \"bones\")\r\n\t\t\t{\r\n\t\t\t\tvar bones = [];\r\n\t\t\t\tvar num_bones = Number(t[1]);\r\n\t\t\t\tfor(var j = 0; j < num_bones; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar m = (t.slice(3 + j*17, 3 + (j+1)*17 - 1)).map(Number);\r\n\t\t\t\t\tbones.push( [ t[2 + j*17], m ] );\r\n\t\t\t\t}\r\n\t\t\t\tmesh.bones = bones;\r\n\t\t\t}\r\n\t\t\telse if(name == \"bind_matrix\")\r\n\t\t\t\tmesh.bind_matrix = t.slice(1,17).map(Number);\r\n\t\t\telse if(name == \"groups\")\r\n\t\t\t{\r\n\t\t\t\tmesh.info = { groups: [] };\r\n\t\t\t\tvar num_groups = Number(t[1]);\r\n\t\t\t\tfor(var j = 0; j < num_groups; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar 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]) };\r\n\t\t\t\t\tmesh.info.groups.push(group);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t\tconsole.warn(\"type unknown: \" + t[0] );\r\n\t}\r\n\r\n\tif(options.only_data)\r\n\t\treturn mesh;\r\n\r\n\t//creates and returns a GL.Mesh\r\n\tvar final_mesh = null;\r\n\tfinal_mesh = Mesh.load( mesh, null, options.mesh );\r\n\tfinal_mesh.updateBoundingBox();\r\n\treturn final_mesh;\r\n}\r\n\r\nMesh.encoders[\"mesh\"] = function( mesh, options )\r\n{\r\n\tvar lines = [];\r\n\tfor(var i in mesh.vertexBuffers )\r\n\t{\r\n\t\tvar buffer = mesh.vertexBuffers[i];\r\n\t\tvar line = [\"-\"+i, buffer.data.length, buffer.data, typedArrayToArray( buffer.data ) ];\r\n\t\tlines.push(line.join(\",\"));\r\n\t}\r\n\r\n\tfor(var i in mesh.indexBuffers )\r\n\t{\r\n\t\tvar buffer = mesh.indexBuffers[i];\r\n\t\tvar line = [ \"*\" + i, buffer.data.length, buffer.data, typedArrayToArray( buffer.data ) ];\r\n\t\tlines.push(line.join(\",\"));\r\n\t}\r\n\r\n\tif(mesh.bounding)\r\n\t\tlines.push( [\"@bounding\", typedArrayToArray(mesh.bounding.subarray(0,6))].join(\",\") );\r\n\tif(mesh.info && mesh.info.groups)\r\n\t{\r\n\t\tvar groups_info = [];\r\n\t\tfor(var j = 0; j < mesh.info.groups.length; ++j)\r\n\t\t{\r\n\t\t\tvar group = mesh.info.groups[j];\r\n\t\t\tgroups_info.push( group.name, group.material, group.start, group.length );\r\n\t\t}\r\n\t\tlines.push( [\"@groups\", mesh.info.groups.length ].concat( groups_info ).join(\",\")  );\r\n\t}\r\n\r\n\tif(mesh.bones)\r\n\t\tlines.push( [\"@bones\", mesh.bones.length, mesh.bones.flat()].join(\",\") );\r\n\tif(mesh.bind_matrix)\r\n\t\tlines.push( [\"@bind_matrix\", typedArrayToArray(mesh.bind_matrix) ].join(\",\") );\r\n\r\n\treturn lines.join(\"\\n\");\r\n}\r\n\r\n/* BINARY FORMAT ************************************/\r\n\r\nif(global.WBin)\r\n\tglobal.WBin.classes[\"Mesh\"] = Mesh;\r\n\r\nMesh.binary_file_formats[\"wbin\"] = true;\r\n\r\nMesh.parsers[\"wbin\"] = Mesh.fromBinary = function( data_array, options )\r\n{\r\n\tif(!global.WBin)\r\n\t\tthrow(\"To use binary meshes you need to install WBin.js from https://github.com/jagenjo/litescene.js/blob/master/src/utils/wbin.js \");\r\n\r\n\toptions = options || {};\r\n\r\n\tvar o = null;\r\n\tif( data_array.constructor == ArrayBuffer )\r\n\t\to = WBin.load( data_array, true );\r\n\telse\r\n\t\to = data_array;\r\n\r\n\tif(!o.info)\r\n\t\tconsole.warn(\"This WBin doesn't seem to contain a mesh. Classname: \", o[\"@classname\"] );\r\n\r\n\tif( o.format )\r\n\t\tGL.Mesh.decompress( o );\r\n\r\n\tvar vertex_buffers = {};\r\n\tif(o.vertex_buffers)\r\n\t{\r\n\t\tfor(var i in o.vertex_buffers)\r\n\t\t\tvertex_buffers[ o.vertex_buffers[i] ] = o[ o.vertex_buffers[i] ];\r\n\t}\r\n\telse\r\n\t{\r\n\t\tif(o.vertices) vertex_buffers.vertices = o.vertices;\r\n\t\tif(o.normals) vertex_buffers.normals = o.normals;\r\n\t\tif(o.coords) vertex_buffers.coords = o.coords;\r\n\t\tif(o.weights) vertex_buffers.weights = o.weights;\r\n\t\tif(o.bone_indices) vertex_buffers.bone_indices = o.bone_indices;\r\n\t}\r\n\r\n\tvar index_buffers = {};\r\n\tif( o.index_buffers )\r\n\t{\r\n\t\tfor(var i in o.index_buffers)\r\n\t\t\tindex_buffers[ o.index_buffers[i] ] = o[ o.index_buffers[i] ];\r\n\t}\r\n\telse\r\n\t{\r\n\t\tif(o.triangles) index_buffers.triangles = o.triangles;\r\n\t\tif(o.wireframe) index_buffers.wireframe = o.wireframe;\r\n\t}\r\n\r\n\tvar mesh = { \r\n\t\tvertex_buffers: vertex_buffers,\r\n\t\tindex_buffers: index_buffers,\r\n\t\tbounding: o.bounding,\r\n\t\tinfo: o.info\r\n\t};\r\n\r\n\tif(o.bones)\r\n\t{\r\n\t\tmesh.bones = o.bones;\r\n\t\t//restore Float32array\r\n\t\tfor(var i = 0; i < mesh.bones.length; ++i)\r\n\t\t\tmesh.bones[i][1] = mat4.clone(mesh.bones[i][1]);\r\n\t\tif(o.bind_matrix)\r\n\t\t\tmesh.bind_matrix = mat4.clone( o.bind_matrix );\t\t\r\n\t}\r\n\r\n\tif(o.morph_targets)\r\n\t\tmesh.morph_targets = o.morph_targets;\r\n\r\n\tif(options.only_data)\r\n\t\treturn mesh;\r\n\r\n\t//build mesh object\r\n\tvar final_mesh = options.mesh || new GL.Mesh();\r\n\tfinal_mesh.configure( mesh );\r\n\treturn final_mesh;\r\n}\r\n\r\nMesh.encoders[\"wbin\"] = function( mesh, options )\r\n{\r\n\treturn mesh.toBinary( options );\r\n}\r\n\r\nMesh.prototype.toBinary = function( options )\r\n{\r\n\tif(!global.WBin)\r\n\t\tthrow(\"to use Mesh.toBinary you need to have WBin included. Check the repository for wbin.js\");\r\n\r\n\tif(!this.info)\r\n\t\tthis.info = {};\r\n\r\n\t//clean data\r\n\tvar o = {\r\n\t\tobject_class: \"Mesh\",\r\n\t\tinfo: this.info,\r\n\t\tgroups: this.groups\r\n\t};\r\n\r\n\tif(this.bones)\r\n\t{\r\n\t\tvar bones = [];\r\n\t\t//convert to array\r\n\t\tfor(var i = 0; i < this.bones.length; ++i)\r\n\t\t\tbones.push([ this.bones[i][0], mat4.toArray( this.bones[i][1] ) ]);\r\n\t\to.bones = bones;\r\n\t\tif(this.bind_matrix)\r\n\t\t\to.bind_matrix = this.bind_matrix;\r\n\t}\r\n\r\n\t//bounding box\r\n\tif(!this.bounding)\t\r\n\t\tthis.updateBoundingBox();\r\n\to.bounding = this.bounding;\r\n\r\n\tvar vertex_buffers = [];\r\n\tvar index_buffers = [];\r\n\r\n\tfor(var i in this.vertexBuffers)\r\n\t{\r\n\t\tvar stream = this.vertexBuffers[i];\r\n\t\to[ stream.name ] = stream.data;\r\n\t\tvertex_buffers.push( stream.name );\r\n\r\n\t\tif(stream.name == \"vertices\")\r\n\t\t\to.info.num_vertices = stream.data.length / 3;\r\n\t}\r\n\r\n\tfor(var i in this.indexBuffers)\r\n\t{\r\n\t\tvar stream = this.indexBuffers[i];\r\n\t\to[i] = stream.data;\r\n\t\tindex_buffers.push( i );\r\n\t}\r\n\r\n\to.vertex_buffers = vertex_buffers;\r\n\to.index_buffers = index_buffers;\r\n\r\n\t//compress wbin using the bounding\r\n\tif( GL.Mesh.enable_wbin_compression ) //apply compression\r\n\t\tGL.Mesh.compress( o );\r\n\r\n\t//create pack file\r\n\tvar bin = WBin.create( o, \"Mesh\" ); \r\n\treturn bin;\r\n}\r\n\r\nMesh.compress = function( o, format )\r\n{\r\n\tformat = format || \"bounding_compressed\";\r\n\to.format = {\r\n\t\ttype: format\r\n\t};\r\n\r\n\tvar func = Mesh.compressors[ format ];\r\n\tif(!func)\r\n\t\tthrow(\"compression format not supported:\" + format );\r\n\treturn func( o );\r\n}\r\n\r\nMesh.decompress = function( o )\r\n{\r\n\tif(!o.format)\r\n\t\treturn;\r\n\tvar func = Mesh.decompressors[ o.format.type ];\r\n\tif(!func)\r\n\t\tthrow(\"decompression format not supported:\" + o.format.type );\r\n\treturn func( o );\r\n}\r\n\r\nMesh.compressors[\"bounding_compressed\"] = function(o)\r\n{\r\n\tif(!o.vertex_buffers)\r\n\t\tthrow(\"buffers not found\");\r\n\r\n\tvar min = BBox.getMin( o.bounding );\r\n\tvar max = BBox.getMax( o.bounding );\r\n\tvar range = vec3.sub( vec3.create(), max, min );\r\n\r\n\tvar vertices = o.vertices;\r\n\tvar new_vertices = new Uint16Array( vertices.length );\r\n\tfor(var i = 0; i < vertices.length; i+=3)\r\n\t{\r\n\t\tnew_vertices[i] = ((vertices[i] - min[0]) / range[0]) * 65535;\r\n\t\tnew_vertices[i+1] = ((vertices[i+1] - min[1]) / range[1]) * 65535;\r\n\t\tnew_vertices[i+2] = ((vertices[i+2] - min[2]) / range[2]) * 65535;\r\n\t}\r\n\to.vertices = new_vertices;\t\t\r\n\r\n\tif( o.normals )\r\n\t{\r\n\t\tvar normals = o.normals;\r\n\t\tvar new_normals = new Uint8Array( normals.length );\r\n\t\tvar normals_range = new_normals.constructor == Uint8Array ? 255 : 65535;\r\n\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t{\r\n\t\t\tnew_normals[i] = (normals[i] * 0.5 + 0.5) * normals_range;\r\n\t\t\tnew_normals[i+1] = (normals[i+1] * 0.5 + 0.5) * normals_range;\r\n\t\t\tnew_normals[i+2] = (normals[i+2] * 0.5 + 0.5) * normals_range;\r\n\t\t}\r\n\t\to.normals = new_normals;\r\n\t}\r\n\r\n\tif( o.coords )\r\n\t{\r\n\t\t//compute uv bounding: [minu,minv,maxu,maxv]\r\n\t\tvar coords = o.coords;\r\n\t\tvar uvs_bounding = [10000,10000,-10000,-10000];\r\n\t\tfor(var i = 0; i < coords.length; i+=2)\r\n\t\t{\r\n\t\t\tvar u = coords[i];\r\n\t\t\tif( uvs_bounding[0] > u ) uvs_bounding[0] = u;\r\n\t\t\telse if( uvs_bounding[2] < u ) uvs_bounding[2] = u;\r\n\t\t\tvar v = coords[i+1];\r\n\t\t\tif( uvs_bounding[1] > v ) uvs_bounding[1] = v;\r\n\t\t\telse if( uvs_bounding[3] < v ) uvs_bounding[3] = v;\r\n\t\t}\r\n\t\to.format.uvs_bounding = uvs_bounding;\r\n\r\n\t\tvar new_coords = new Uint16Array( coords.length );\r\n\t\tvar range = [ uvs_bounding[2] - uvs_bounding[0], uvs_bounding[3] - uvs_bounding[1] ];\r\n\t\tfor(var i = 0; i < coords.length; i+=2)\r\n\t\t{\r\n\t\t\tnew_coords[i] = ((coords[i] - uvs_bounding[0]) / range[0]) * 65535;\r\n\t\t\tnew_coords[i+1] = ((coords[i+1] - uvs_bounding[1]) / range[1]) * 65535;\r\n\t\t}\r\n\t\to.coords = new_coords;\r\n\t}\r\n\r\n\tif( o.weights )\r\n\t{\r\n\t\tvar weights = o.weights;\r\n\t\tvar new_weights = new Uint16Array( weights.length ); //using only one byte distorts the meshes a lot\r\n\t\tvar weights_range = new_weights.constructor == Uint8Array ? 255 : 65535;\r\n\t\tfor(var i = 0; i < weights.length; i+=4)\r\n\t\t{\r\n\t\t\tnew_weights[i] = weights[i] * weights_range;\r\n\t\t\tnew_weights[i+1] = weights[i+1] * weights_range;\r\n\t\t\tnew_weights[i+2] = weights[i+2] * weights_range;\r\n\t\t\tnew_weights[i+3] = weights[i+3] * weights_range;\r\n\t\t}\r\n\t\to.weights = new_weights;\r\n\t}\r\n}\r\n\r\n\r\nMesh.decompressors[\"bounding_compressed\"] = function(o)\r\n{\r\n\tvar bounding = o.bounding;\r\n\tif(!bounding)\r\n\t\tthrow(\"error in mesh decompressing data: bounding not found, cannot use the bounding decompression.\");\r\n\r\n\tvar min = BBox.getMin( bounding );\r\n\tvar max = BBox.getMax( bounding );\r\n\tvar range = vec3.sub( vec3.create(), max, min );\r\n\r\n\tvar format = o.format;\r\n\r\n\tvar inv8 = 1 / 255;\r\n\tvar inv16 = 1 / 65535;\r\n\tvar vertices = o.vertices;\r\n\tvar new_vertices = new Float32Array( vertices.length );\r\n\tfor( var i = 0, l = vertices.length; i < l; i += 3 )\r\n\t{\r\n\t\tnew_vertices[i] = ((vertices[i] * inv16) * range[0]) + min[0];\r\n\t\tnew_vertices[i+1] = ((vertices[i+1] * inv16) * range[1]) + min[1];\r\n\t\tnew_vertices[i+2] = ((vertices[i+2] * inv16) * range[2]) + min[2];\r\n\t}\r\n\to.vertices = new_vertices;\t\t\r\n\r\n\tif( o.normals && o.normals.constructor != Float32Array )\r\n\t{\r\n\t\tvar normals = o.normals;\r\n\t\tvar new_normals = new Float32Array( normals.length );\r\n\t\tvar inormals_range = normals.constructor == Uint8Array ? inv8 : inv16;\r\n\t\tfor( var i = 0, l = normals.length; i < l; i += 3 )\r\n\t\t{\r\n\t\t\tnew_normals[i] = (normals[i] * inormals_range) * 2.0 - 1.0;\r\n\t\t\tnew_normals[i+1] = (normals[i+1] * inormals_range) * 2.0 - 1.0;\r\n\t\t\tnew_normals[i+2] = (normals[i+2] * inormals_range) * 2.0 - 1.0;\r\n\t\t\tvar N = new_normals.subarray(i,i+3);\r\n\t\t\tvec3.normalize(N,N);\r\n\t\t}\r\n\t\to.normals = new_normals;\r\n\t}\r\n\r\n\tif( o.coords && format.uvs_bounding && o.coords.constructor != Float32Array )\r\n\t{\r\n\t\tvar coords = o.coords;\r\n\t\tvar uvs_bounding = format.uvs_bounding;\r\n\t\tvar range = [ uvs_bounding[2] - uvs_bounding[0], uvs_bounding[3] - uvs_bounding[1] ];\r\n\t\tvar new_coords = new Float32Array( coords.length );\r\n\t\tfor( var i = 0, l = coords.length; i < l; i += 2 )\r\n\t\t{\r\n\t\t\tnew_coords[i] = (coords[i] * inv16) * range[0] + uvs_bounding[0];\r\n\t\t\tnew_coords[i+1] = (coords[i+1] * inv16) * range[1] + uvs_bounding[1];\r\n\t\t}\r\n\t\to.coords = new_coords;\r\n\t}\r\n\r\n\t//bones are already in Uint8 format so dont need to compress them further, but weights yes\r\n\tif( o.weights && o.weights.constructor != Float32Array ) //do we really need to unpack them? what if we use them like this?\r\n\t{\r\n\t\tvar weights = o.weights;\r\n\t\tvar new_weights = new Float32Array( weights.length );\r\n\t\tvar iweights_range = weights.constructor == Uint8Array ? inv8 : inv16;\r\n\t\tfor(var i = 0, l = weights.length; i < l; i += 4 )\r\n\t\t{\r\n\t\t\tnew_weights[i] = weights[i] * iweights_range;\r\n\t\t\tnew_weights[i+1] = weights[i+1] * iweights_range;\r\n\t\t\tnew_weights[i+2] = weights[i+2] * iweights_range;\r\n\t\t\tnew_weights[i+3] = weights[i+3] * iweights_range;\r\n\t\t}\r\n\t\to.weights = new_weights;\r\n\t}\r\n}\n\r\n//footer.js\r\n})( typeof(window) != \"undefined\" ? window : (typeof(self) != \"undefined\" ? self : global ) );\n"
  },
  {
    "path": "editor/js/libs/midi-parser.js",
    "content": "/*\n    Project Name : midi-parser-js\n    Project Url  : https://github.com/colxi/midi-parser-js/\n    Author       : colxi\n    Author URL   : http://www.colxi.info/\n    Description  : MidiParser library reads .MID binary files, Base64 encoded MIDI Data,\n    or UInt8 Arrays, and outputs as a readable and structured JS object.\n*/\n\n(function(){\n    'use strict';\n\n    /**\n     * CROSSBROWSER & NODEjs POLYFILL for ATOB() -\n     * By: https://github.com/MaxArt2501 (modified)\n     * @param  {string} string [description]\n     * @return {[type]}        [description]\n     */\n    const _atob = function(string) {\n        // base64 character set, plus padding character (=)\n        let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';\n        // Regular expression to check formal correctness of base64 encoded strings\n        let b64re = /^(?:[A-Za-z\\d+\\/]{4})*?(?:[A-Za-z\\d+\\/]{2}(?:==)?|[A-Za-z\\d+\\/]{3}=?)?$/;\n        // remove data type signatures at the begining of the string\n        // eg :  \"data:audio/mid;base64,\"\n        string = string.replace( /^.*?base64,/ , '');\n        // atob can work with strings with whitespaces, even inside the encoded part,\n        // but only \\t, \\n, \\f, \\r and ' ', which can be stripped.\n        string = String(string).replace(/[\\t\\n\\f\\r ]+/g, '');\n        if (!b64re.test(string))\n            throw new TypeError('Failed to execute _atob() : The string to be decoded is not correctly encoded.');\n\n        // Adding the padding if missing, for semplicity\n        string += '=='.slice(2 - (string.length & 3));\n        let bitmap, result = '';\n        let r1, r2, i = 0;\n        for (; i < string.length;) {\n            bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12\n                    | (r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++)));\n\n            result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255)\n                    : r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255)\n                    : String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255);\n        }\n        return result;\n    };\n\n\n    /**\n     * [MidiParser description]\n     * @type {Object}\n     */\n    const MidiParser  = {\n        // debug (bool), when enabled will log in console unimplemented events\n        // warnings and internal handled errors.\n        debug: false,\n\n        /**\n         * [parse description]\n         * @param  {[type]} input     [description]\n         * @param  {[type]} _callback [description]\n         * @return {[type]}           [description]\n         */\n        parse: function(input, _callback){\n            if(input instanceof Uint8Array) return MidiParser.Uint8(input);\n            else if(typeof input === 'string') return MidiParser.Base64(input);\n            else if(input instanceof HTMLElement && input.type === 'file') return MidiParser.addListener(input , _callback);\n            else throw new Error('MidiParser.parse() : Invalid input provided');\n        },\n\n        /**\n         * addListener() should be called in order attach a listener to the INPUT HTML element\n         * that will provide the binary data automating the conversion, and returning\n         * the structured data to the provided callback function.\n         */\n        addListener: function(_fileElement, _callback){\n            if(!File || !FileReader) throw new Error('The File|FileReader APIs are not supported in this browser. Use instead MidiParser.Base64() or MidiParser.Uint8()');\n\n            // validate provided element\n            if( _fileElement === undefined ||\n                !(_fileElement instanceof HTMLElement) ||\n                _fileElement.tagName !== 'INPUT' ||\n                _fileElement.type.toLowerCase() !== 'file' \n            ){\n                console.warn('MidiParser.addListener() : Provided element is not a valid FILE INPUT element');\n                return false;\n            }\n            _callback = _callback || function(){};\n\n            _fileElement.addEventListener('change', function(InputEvt){             // set the 'file selected' event handler\n                if (!InputEvt.target.files.length) return false;                    // return false if no elements where selected\n                console.log('MidiParser.addListener() : File detected in INPUT ELEMENT processing data..');\n                let reader = new FileReader();                                      // prepare the file Reader\n                reader.readAsArrayBuffer(InputEvt.target.files[0]);                 // read the binary data\n                reader.onload =  function(e){\n                    _callback( MidiParser.Uint8(new Uint8Array(e.target.result)));  // encode data with Uint8Array and call the parser\n                };\n            });\n        },\n\n        /**\n         * Base64() : convert baset4 string into uint8 array buffer, before performing the\n         * parsing subroutine.\n         */\n        Base64 : function(b64String){\n            b64String = String(b64String);\n\n            let raw = _atob(b64String);\n            let rawLength = raw.length;\n            let t_array = new Uint8Array(new ArrayBuffer(rawLength));\n\n            for(let i=0; i<rawLength; i++) t_array[i] = raw.charCodeAt(i);\n            return  MidiParser.Uint8(t_array) ;\n        },\n\n        /**\n         * parse() : function reads the binary data, interpreting and spliting each chuck\n         * and parsing it to a structured Object. When job is finised returns the object\n         * or 'false' if any error was generated.\n         */\n        Uint8: function(FileAsUint8Array){\n            let file = {\n                data: null,\n                pointer: 0,\n                movePointer: function(_bytes){                                      // move the pointer negative and positive direction\n                    this.pointer += _bytes;\n                    return this.pointer;\n                },\n                readInt: function(_bytes){                                          // get integer from next _bytes group (big-endian)\n                    _bytes = Math.min(_bytes, this.data.byteLength-this.pointer);\n                    if (_bytes < 1) return -1;                                                                      // EOF\n                    let value = 0;\n                    if(_bytes > 1){\n                        for(let i=1; i<= (_bytes-1); i++){\n                            value += this.data.getUint8(this.pointer) * Math.pow(256, (_bytes - i));\n                            this.pointer++;\n                        }\n                    }\n                    value += this.data.getUint8(this.pointer);\n                    this.pointer++;\n                    return value;\n                },\n                readStr: function(_bytes){                                          // read as ASCII chars, the followoing _bytes\n                    let text = '';\n                    for(let char=1; char <= _bytes; char++) text +=  String.fromCharCode(this.readInt(1));\n                    return text;\n                },\n                readIntVLV: function(){                                             // read a variable length value\n                    let value = 0;\n                    if ( this.pointer >= this.data.byteLength ){\n                        return -1;                                                  // EOF\n                    }else if(this.data.getUint8(this.pointer) < 128){               // ...value in a single byte\n                        value = this.readInt(1);\n                    }else{                                                          // ...value in multiple bytes\n                        let FirstBytes = [];\n                        while(this.data.getUint8(this.pointer) >= 128){\n                            FirstBytes.push(this.readInt(1) - 128);\n                        }\n                        let lastByte  = this.readInt(1);\n                        for(let dt = 1; dt <= FirstBytes.length; dt++){\n                            value += FirstBytes[FirstBytes.length - dt] * Math.pow(128, dt);\n                        }\n                        value += lastByte;\n                    }\n                    return value;\n                }\n            };\n\n            file.data = new DataView(FileAsUint8Array.buffer, FileAsUint8Array.byteOffset, FileAsUint8Array.byteLength);                                            // 8 bits bytes file data array\n            //  ** read FILE HEADER\n            if(file.readInt(4) !== 0x4D546864){\n                console.warn('Header validation failed (not MIDI standard or file corrupt.)');\n                return false;                                                       // Header validation failed (not MIDI standard or file corrupt.)\n            }\n            let headerSize          = file.readInt(4);                              // header size (unused var), getted just for read pointer movement\n            let MIDI                = {};                                           // create new midi object\n            MIDI.formatType         = file.readInt(2);                              // get MIDI Format Type\n            MIDI.tracks             = file.readInt(2);                              // get ammount of track chunks\n            MIDI.track              = [];                                           // create array key for track data storing\n            let timeDivisionByte1   = file.readInt(1);                              // get Time Division first byte\n            let timeDivisionByte2   = file.readInt(1);                              // get Time Division second byte\n            if(timeDivisionByte1 >= 128){                                           // discover Time Division mode (fps or tpf)\n                MIDI.timeDivision    = [];\n                MIDI.timeDivision[0] = timeDivisionByte1 - 128;                     // frames per second MODE  (1st byte)\n                MIDI.timeDivision[1] = timeDivisionByte2;                           // ticks in each frame     (2nd byte)\n            }else MIDI.timeDivision  = (timeDivisionByte1 * 256) + timeDivisionByte2;// else... ticks per beat MODE  (2 bytes value)\n\n            //  ** read TRACK CHUNK\n            for(let t=1; t <= MIDI.tracks; t++){\n                MIDI.track[t-1]     = {event: []};                                  // create new Track entry in Array\n                let headerValidation = file.readInt(4);\n                if ( headerValidation === -1 ) break;                               // EOF\n                if(headerValidation !== 0x4D54726B) return false;                   // Track chunk header validation failed.\n                file.readInt(4);                                                    // move pointer. get chunk size (bytes length)\n                let e               = 0;                                            // init event counter\n                let endOfTrack      = false;                                        // FLAG for track reading secuence breaking\n                // ** read EVENT CHUNK\n                let statusByte;\n                let laststatusByte;\n                while(!endOfTrack){\n                    e++;                                                            // increase by 1 event counter\n                    MIDI.track[t-1].event[e-1] = {};                                // create new event object, in events array\n                    MIDI.track[t-1].event[e-1].deltaTime  = file.readIntVLV();      // get DELTA TIME OF MIDI event (Variable Length Value)\n                    statusByte = file.readInt(1);                                   // read EVENT TYPE (STATUS BYTE)\n                    if(statusByte === -1) break;                                    // EOF\n                    else if(statusByte >= 128) laststatusByte = statusByte;         // NEW STATUS BYTE DETECTED\n                    else{                                                           // 'RUNNING STATUS' situation detected\n                        statusByte = laststatusByte;                                // apply last loop, Status Byte\n                        file.movePointer(-1);                                       // move back the pointer (cause readed byte is not status byte)\n                    }\n\n\n                    //\n                    // ** IS META EVENT\n                    //\n                    if(statusByte === 0xFF){                                        // Meta Event type\n                        MIDI.track[t-1].event[e-1].type = 0xFF;                     // assign metaEvent code to array\n                        MIDI.track[t-1].event[e-1].metaType =  file.readInt(1);     // assign metaEvent subtype\n                        let metaEventLength = file.readIntVLV();                    // get the metaEvent length\n                        switch(MIDI.track[t-1].event[e-1].metaType){\n                            case 0x2F:                                              // end of track, has no data byte\n                            case -1:                                                // EOF\n                                endOfTrack = true;                                  // change FLAG to force track reading loop breaking\n                                break;\n                            case 0x01:                                              // Text Event\n                            case 0x02:                                              // Copyright Notice\n                            case 0x03:\n                            case 0x04:                                              // Instrument Name\n                            case 0x05:                                              // Lyrics)\n                            case 0x07:                                              // Cue point                                         // Sequence/Track Name (documentation: http://www.ta7.de/txt/musik/musi0006.htm)\n                            case 0x06:                                              // Marker\n                                MIDI.track[t-1].event[e-1].data = file.readStr(metaEventLength);\n                                break;\n                            case 0x21:                                              // MIDI PORT\n                            case 0x59:                                              // Key Signature\n                            case 0x51:                                              // Set Tempo\n                                MIDI.track[t-1].event[e-1].data = file.readInt(metaEventLength);\n                                break;\n                            case 0x54:                                              // SMPTE Offset\n                                MIDI.track[t-1].event[e-1].data    = [];\n                                MIDI.track[t-1].event[e-1].data[0] = file.readInt(1);\n                                MIDI.track[t-1].event[e-1].data[1] = file.readInt(1);\n                                MIDI.track[t-1].event[e-1].data[2] = file.readInt(1);\n                                MIDI.track[t-1].event[e-1].data[3] = file.readInt(1);\n                                MIDI.track[t-1].event[e-1].data[4] = file.readInt(1);\n                                break;\n                            case 0x58:                                              // Time Signature\n                                MIDI.track[t-1].event[e-1].data    = [];\n                                MIDI.track[t-1].event[e-1].data[0] = file.readInt(1);\n                                MIDI.track[t-1].event[e-1].data[1] = file.readInt(1);\n                                MIDI.track[t-1].event[e-1].data[2] = file.readInt(1);\n                                MIDI.track[t-1].event[e-1].data[3] = file.readInt(1);\n                                break;\n                            default :\n                                // if user provided a custom interpreter, call it\n                                // and assign to event the returned data\n                                if( this.customInterpreter !== null){\n                                    MIDI.track[t-1].event[e-1].data = this.customInterpreter( MIDI.track[t-1].event[e-1].metaType, file, metaEventLength);\n                                }\n                                // if no customInterpretr is provided, or returned\n                                // false (=apply default), perform default action\n                                if(this.customInterpreter === null || MIDI.track[t-1].event[e-1].data === false){\n                                    file.readInt(metaEventLength);\n                                    MIDI.track[t-1].event[e-1].data = file.readInt(metaEventLength);\n                                    if (this.debug) console.info('Unimplemented 0xFF meta event! data block readed as Integer');\n                                }\n                        }\n                    }\n\n                    //\n                    // IS REGULAR EVENT\n                    //\n                    else{                                                           // MIDI Control Events OR System Exclusive Events\n                        statusByte = statusByte.toString(16).split('');             // split the status byte HEX representation, to obtain 4 bits values\n                        if(!statusByte[1]) statusByte.unshift('0');                 // force 2 digits\n                        MIDI.track[t-1].event[e-1].type = parseInt(statusByte[0], 16);// first byte is EVENT TYPE ID\n                        MIDI.track[t-1].event[e-1].channel = parseInt(statusByte[1], 16);// second byte is channel\n                        switch(MIDI.track[t-1].event[e-1].type){\n                            case 0xF:{                                              // System Exclusive Events\n\n                                // if user provided a custom interpreter, call it\n                                // and assign to event the returned data\n                                if( this.customInterpreter !== null){\n                                    MIDI.track[t-1].event[e-1].data = this.customInterpreter( MIDI.track[t-1].event[e-1].type, file , false);\n                                }\n\n                                // if no customInterpretr is provided, or returned\n                                // false (=apply default), perform default action\n                                if(this.customInterpreter === null || MIDI.track[t-1].event[e-1].data === false){\n                                    let event_length = file.readIntVLV();\n                                    MIDI.track[t-1].event[e-1].data = file.readInt(event_length);\n                                    if (this.debug) console.info('Unimplemented 0xF exclusive events! data block readed as Integer');\n                                }\n                                break;\n                            }\n                            case 0xA:                                               // Note Aftertouch\n                            case 0xB:                                               // Controller\n                            case 0xE:                                               // Pitch Bend Event\n                            case 0x8:                                               // Note off\n                            case 0x9:                                               // Note On\n                                MIDI.track[t-1].event[e-1].data = [];\n                                MIDI.track[t-1].event[e-1].data[0] = file.readInt(1);\n                                MIDI.track[t-1].event[e-1].data[1] = file.readInt(1);\n                                break;\n                            case 0xC:                                               // Program Change\n                            case 0xD:                                               // Channel Aftertouch\n                                MIDI.track[t-1].event[e-1].data = file.readInt(1);\n                                break;\n                            case -1:                                                // EOF\n                                endOfTrack = true;                                  // change FLAG to force track reading loop breaking\n                                break;\n                            default:\n                                // if user provided a custom interpreter, call it\n                                // and assign to event the returned data\n                                if( this.customInterpreter !== null){\n                                    MIDI.track[t-1].event[e-1].data = this.customInterpreter( MIDI.track[t-1].event[e-1].metaType, file , false);\n                                }\n\n                                // if no customInterpretr is provided, or returned\n                                // false (=apply default), perform default action\n                                if(this.customInterpreter === null || MIDI.track[t-1].event[e-1].data === false){\n                                    console.log('Unknown EVENT detected... reading cancelled!');\n                                    return false;\n                                }\n                        }\n                    }\n                }\n            }\n            return MIDI;\n        },\n\n        /**\n         * custom function to handle unimplemented, or custom midi messages.\n         * If message is a meta-event, the value of metaEventLength will be >0.\n         * Function must return the value to store, and pointer of dataView needs\n         * to be manually increased\n         * If you want default action to be performed, return false\n         */\n        customInterpreter : null // function( e_type , arrayByffer, metaEventLength){ return e_data_int }\n    };\n\n\n    // if running in NODE export module\n    if(typeof module !== 'undefined') module.exports = MidiParser;\n    else{\n        // if running in Browser, set a global variable.\n        let _global = typeof window === 'object' && window.self === window && window ||\n                    typeof self === 'object' && self.self === self && self ||\n                    typeof global === 'object' && global.global === global && global;\n\n        _global.MidiParser = MidiParser;\n    }\n\n\n    \n})();"
  },
  {
    "path": "editor/style.css",
    "content": "html,body {\n\twidth: 100%;\n\theight: 100%;\n\tmargin: 0;\n\tpadding: 0;\n}\n\nbody {\n\tbackground-color: #333;\n\tcolor: #EEE;\n\tfont: 14px Tahoma;\n}\n\nh1 {\n\tfont-family: \"Metro Light\",Tahoma;\n}\n\nh2 {\n\tfont-family: \"Metro Light\";\n}\n\n#main {\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: #222;\n}\n\n\n#status {\n\tposition: absolute;\n\ttop: 10px;\n\tright: 10px;\n\tcolor: #FAA;\n\tfont-size: 18px;\n\tpadding: 5px;\n\t/*border-radius: 5px;*/\n\twidth: -moz-calc( 50% - 30px);\n\tmin-height: 30px;\n\toverflow: hidden;\n\tbackground-color: #644;\n}\n\n#help-message {\n\tpadding: 2px;\n\tfont-size: 0.8em;\n\tbackground-color: #464;\n\t/*border-radius: 2px;*/\n}\n\n#content {\n\tposition: relative;\n\tmin-height: 500px;\n\toverflow: hidden;\n}\n\n.fullscreen #content {\n\tmin-height: -moz-calc(100% - 80px);\n\tmin-height: -webkit-calc(100% - 80px);\n\tmin-height: calc(100% - 80px);\n}\n\n.info-section p {\n\tpadding-left: 20px;\n\tmargin: 2px;\n}\n\n.info-section strong {\n\tcolor: #FEA;\n}\n\n#visual {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tbackground-color: black;\n\twidth: 100%;\n\theight: 100%;\n}\n\n\n.item-list .item {\n\tmargin: 5px;\n\tpadding: 5px;\n\tfont-size: 1.2em;\n\n\tbackground-color: transparent;\n\tcolor: #999;\n\tpadding-left: 5px;\n\ttransition: background-color 300ms, color 300ms, padding-left 300ms;\n\t-moz-transition: background-color 300ms, color 300ms, padding-left 300ms;\n\t-webkit-transition: background-color 300ms, color 300ms, padding-left 300ms;\n}\n\n.item-list .item:hover {\n\tbackground-color: #33A;\n\t/*border-radius: 4px;*/\n\tcolor: white;\n\tpadding-left: 15px;\n\ttransition: background-color 300ms, color 300ms, padding-left 300ms;\n\t-moz-transition: background-color 300ms, color 300ms, padding-left 300ms;\n\t-webkit-transition: background-color 300ms, color 300ms, padding-left 300ms;\n\tcursor: pointer;\n}\n\n#gallery .item-list .item:hover {\n\tbackground-color: #A83;\n}\n\n.item-list .item strong {\n\tdisplay: inline-block;\n\twidth: 200px;\n}\n\n.form label {\n\tfont-size: 1.2em;\n\twidth: 200px;\n\tdisplay: inline-block;\n\ttext-align: right;\n}\n\nlabel {\n\tfont-weight: bold;\n\tcolor: #AAF;\n}\n\n.header input { \n\tcolor: #EEE;\n\tbackground-color: #555;\n\tfont-size: 1.2em;\n\tborder: 1px solid black;\n\t/*border-radius: 4px;*/\n\tpadding: 2px;\n\t/*box-shadow: inset 0 0 3px #333; */\n\tfont-family: Verdana;\n\twidth: 250px;\n}\n\ntextarea {\n\tvertical-align: top;\n}\n\n#block-app {\n\twidth:100%;\n\theight:100%;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tbackground-color: rgba(0,0,0,0.5);\n\ttext-align: center;\n\tz-index: 6;\n}\n\n#block-app span {\n\tdisplay: block;\n\tfont-size: 30px;\n\tmargin: auto;\n\tmargin-top: 300px;\n}\n\n#block-app span a {\n\tdisplay: inline-block;\n\t/*border-radius: 4px;*/\n\ttext-decoration: none;\n\tcolor: black;\n\tbackground-color: red;\n\tpadding: 0 4px 0 4px;\n}\n\n::-webkit-scrollbar {\n    height: 12px;\n\twidth: 6px;\n\tbackground: #222;\n}\n::-webkit-scrollbar-thumb {\n    background: rgba(200,200,200,0.4);\n}\n::-webkit-scrollbar-corner {\n    background: #766;\n}\n\n#editor {\n\tposition: relative;\n\twidth: 50%;\n\theight: 100%;\n\tdisplay: inline-block;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n#editor .toolsbar {\n\twidth: 100%;\n\theight: 30px;\n\tbackground-color: #262626;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n#editor .toolsbar button {\n\tpadding: 2px;\n\tpadding-left: 10px;\n\tpadding-right: 10px;\n\tmargin: 3px 0 0 3px;\n}\n\n#editor .toolsbar button.enabled {\n\tbackground-color: #66A;\n}\n\n#world {\n\tposition: absolute;\n\ttop: 0;\n\tright: 0;\n\twidth: 50%;\n\theight: 100%;\n}\n\n#worldcanvas {\n\tbackground-color: #343;\n}\n\n.popup {\n\tposition: absolute;\n\ttop: 0;\n\tbackground-color: rgba(50,50,90,0.8);\n\twidth: 100%;\n\theight: 100%;\n}\n\n.popup .header, .nodepanel .header {\n\twidth: 100%;\n\theight: 30px;\n\tfont-size: 20px;\n\tpadding: 2px;\n}\n\n#help {\n\tcolor: #eee;\n}\n\n#help p {\n\tmargin: 10px;\n}\n\n.selector {\n\tfont-size: 1.8em;\n}\n\n\n.selector select {\n\tcolor: white;\n\tbackground-color: black;\n\tborder: 0;\n\tfont-size: 1em;\n}\n\n.graphcanvas{\n\t/*touch-action: manipulation; WONT WORK*/\n\t/*touch-action: none; DESIDERABLE: implement zoom and pan*/\n\ttouch-action: pinch-zoom;\n}"
  },
  {
    "path": "gruntfile.js",
    "content": "module.exports = function (grunt) {\n  grunt.initConfig({\n    pkg: grunt.file.readJSON('package.json'),\n    projectFiles: ['src/litegraph.js',\n      'src/nodes/base.js',\n      'src/nodes/events.js',\n      'src/nodes/interface.js',\n      'src/nodes/input.js',\n      'src/nodes/math.js',\n      'src/nodes/logic.js',\n      'src/nodes/image.js',\n      'src/nodes/gltextures.js',\n      'src/nodes/glfx.js',\n      'src/nodes/midi.js',\n      'src/nodes/audio.js',\n      'src/nodes/network.js'\n    ],\n    concat: {\n      build: {\n        src: '<%= projectFiles %>',\n        dest: 'build/litegraph.js'\n      }\n    },\n    closureCompiler: {\n\n      options: {\n        compilerFile: 'node_modules/google-closure-compiler/compiler.jar',\n        compilerOpts: {\n          formatting: 'pretty_print',\n          warning_level: 'default'\n        },\n        d32: false, // will use 'java -client -d32 -jar compiler.jar'\n        TieredCompilation: false// will use 'java -server -XX:+TieredCompilation -jar compiler.jar',\n        // ,output_wrapper: '\"var LiteGraph = (function(){%output% return LiteGraph;}).call(this);\"'      //* Make container for all\n      },\n      targetName: {\n        src: '<%= projectFiles %>',\n        dest: 'build/litegraph.min.js'\n      }\n    }\n  })\n\n  grunt.loadNpmTasks('grunt-contrib-concat')\n  grunt.loadNpmTasks('grunt-closure-tools')\n\n  grunt.registerTask('build', ['concat:build', 'closureCompiler'])\n}\n"
  },
  {
    "path": "guides/README.md",
    "content": "# LiteGraph\n\nHere is a list of useful info when working with LiteGraph.\nThe library is divided in four levels:\n* **LGraphNode**: the base class of a node (this library uses is own system of inheritance)\n* **LGraph**: the container of a whole graph made of nodes\n* **LGraphCanvas**: the class in charge of rendering/interaction with the nodes inside the browser.\n\nAnd in ```the src/``` folder there is also another class included:\n* **LiteGraph.Editor**: A wrapper around LGraphCanvas that adds buttons around it.\n\n## LGraphNode\n\nLGraphNode is the base class used for all the nodes classes.\nTo extend the other classes all the methods contained in LGraphNode.prototype are copied to the classes when registered.\n\nWhen you create a new node type you do not have to inherit from that class, when the node is registered all the methods are copied to your node prototype.  This is done inside the functions ```LiteGraph.registerNodeType(...)```.\n\nHere is an example of how to create your own node:\n\n```javascript\n//your node constructor class\nfunction MyAddNode()\n{\n  //add some input slots\n  this.addInput(\"A\",\"number\");\n  this.addInput(\"B\",\"number\");\n  //add some output slots\n  this.addOutput(\"A+B\",\"number\");\n  //add some properties\n  this.properties = { precision: 1 };\n}\n\n//name to show on the canvas\nMyAddNode.title = \"Sum\";\n\n//function to call when the node is executed\nMyAddNode.prototype.onExecute = function()\n{\n  //retrieve data from inputs\n  var A = this.getInputData(0);\n  if( A === undefined )\n    A = 0;\n  var B = this.getInputData(1);\n  if( B === undefined )\n    B = 0;\n  //assing data to outputs\n  this.setOutputData( 0, A + B );\n}\n\n//register in the system\nLiteGraph.registerNodeType(\"basic/sum\", MyAddNode );\n\n```\n\n\n## Node settings\n\nThere are several settings that could be defined or modified per node:\n* **size**: ```[width,height]``` the size of the area inside the node (excluding title). Every row is LiteGraph.NODE_SLOT_HEIGHT pixels height.\n* **properties**: object containing the properties that could be configured by the user, and serialized when saving the graph\n* **shape**: the shape of the object (could be LiteGraph.BOX_SHAPE,LiteGraph.ROUND_SHAPE,LiteGraph.CARD_SHAPE)\n* **flags**: flags that can be changed by the user and will be stored when serialized\n  * **collapsed**: if it is shown collapsed (small)\n* **redraw_on_mouse**: forces a redraw if the mouse passes over the widget\n* **widgets_up**: widgets do not start after the slots\n* **widgets_start_y**: widgets should start being drawn from this Y\n* **clip_area**: clips the content when rendering the node\n* **resizable**: if it can be resized dragging the corner\n* **horizontal**: if the slots should be placed horizontally on the top and bottom of the node\n\nThere are several callbacks that could be defined by the user:\n* **onAdded**: called when added to graph\n* **onRemoved**: called when removed from graph\n* **onStart**:\tcalled when the graph starts playing\n* **onStop**:\tcalled when the graph stops playing\n* **onDrawBackground**: render custom node content on canvas (not visible in Live mode)\n* **onDrawForeground**: render custom node content on canvas (on top of slots)\n* **onMouseDown,onMouseMove,onMouseUp,onMouseEnter,onMouseLeave** to catch mouse events\n* **onDblClick**: double clicked in the editor\n* **onExecute**: called when it is time to execute the node\n* **onPropertyChanged**: when a property is changed in the panel (return true to skip default behaviour)\n* **onGetInputs**: returns an array of possible inputs in the form of [ [\"name\",\"type\"], [...], [...] ]\n* **onGetOutputs**: returns an array of possible outputs\n* **onSerialize**: before serializing, receives an object where to store data\n* **onSelected**: selected in the editor, receives an object where to read data\n* **onDeselected**: deselected from the editor\n* **onDropItem**: DOM item dropped over the node\n* **onDropFile**: file dropped over the node\n* **onConnectInput**: if returns false the incoming connection will be canceled\n* **onConnectionsChange**: a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info )\n\n\n### Node slots\n\nEvery node could have several slots, stored in node.inputs and node.outputs.\n\nYou can add new slots by calling node.addInput or node.addOutput\n\nThe main difference between inputs and outputs is that an input can only have one connection link while outputs could have several.\n\nTo get information about an slot you can access node.inputs[ slot_index ]  or node.outputs[ slot_index ]\n\nSlots have the next information:\n\n * **name**: string with the name of the slot (used also to show in the canvas)\n * **type**: string specifying the data type traveling through this link\n * **link or links**: depending if the slot is input or output contains the id of the link or an array of ids\n * **label**: optional, string used to rename the name as shown in the canvas.\n * **dir**: optional, could be LiteGraph.UP, LiteGraph.RIGHT, LiteGraph.DOWN, LiteGraph.LEFT\n * **color_on**: color to render when it is connected\n * **color_off**: color to render when it is not connected\n\n To retrieve the data traveling through a link you can call ```node.getInputData``` or ```node.getOutputData```\n\n### Define your Graph Node\n\nWhen creating a class for a graph node here are some useful points:\n\n- The constructor should create the default inputs and outputs (use ```addInput```  and ```addOutput```)\n- Properties that can be edited are stored in ```this.properties = {};```\n- the ```onExecute``` is the method that will be called when the graph is executed\n- you can catch if a property was changed defining a ```onPropertyChanged```\n- you must register your node using ```LiteGraph.registerNodeType(\"type/name\", MyGraphNodeClass );```\n- you can alter the default priority of execution by defining the ```MyGraphNodeClass.priority``` (default is 0)\n- you can overwrite how the node is rendered using the ```onDrawBackground``` and ```onDrawForeground```\n\n### Custom Node Appearance\n\nYou can configure the node shape or the title color if you want it to be different from the body color:\n```js\nMyNodeClass.title_color = \"#345\";\nMyNodeClass.shape = LiteGraph.ROUND_SHAPE;\n```\n\nYou can draw something inside a node using the callbacks ```onDrawForeground``` and ```onDrawBackground```. The only difference is that onDrawForeground gets called in Live Mode and onDrawBackground not.\n\nBoth functions receive the [Canvas2D rendering context](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) and the LGraphCanvas instance where the node is being rendered.\n\nYou do not have to worry about the coordinates system, (0,0) is the top-left corner of the node content area (not the title).\n\n```js\nnode.onDrawForeground = function(ctx, graphcanvas)\n{\n  if(this.flags.collapsed)\n    return;\n  ctx.save();\n  ctx.fillColor = \"black\";\n  ctx.fillRect(0,0,10,this.size[1]);\n  ctx.restore();\n}\n```\n\n### Custom Node Behaviour\n\nYou can also grab events from the mouse in case your node has some sort of special interactivity.\n\nThe second parameter is the position in node coordinates, where 0,0 represents the top-left corner of the node content (below the title).\n\n```js\nnode.onMouseDown = function( event, pos, graphcanvas )\n{\n    return true; //return true is the event was used by your node, to block other behaviours\n}\n```\n\nOther methods are:\n- onMouseMove\n- onMouseUp\n- onMouseEnter\n- onMouseLeave\n- onKey\n\n### Node Widgets\n\nYou can add widgets inside the node to edit text, values, etc.\n\nTo do so you must create them in the constructor by calling ```node.addWidget```, the returned value is the object containing all the info about the widget, it is handy to store it in case you want to change the value later from code.\n\nThe syntax is:\n\n```js\nfunction MyNodeType()\n{\n  this.slider_widget = this.addWidget(\"slider\",\"Slider\", 0.5, function(value, widget, node){ /* do something with the value */ }, { min: 0, max: 1} );\n}\n```\n\nThis is the list of supported widgets:\n* **\"number\"** to change a value of a number, the syntax is ```this.addWidget(\"number\",\"Number\", current_value, callback, { min: 0, max: 100, step: 1, precision: 3 } );```\n* **\"slider\"** to change a number by dragging the mouse, the syntax is the same as number.\n* **\"combo\"** to select between multiple choices, the syntax is:\n\n  ```this.addWidget(\"combo\",\"Combo\", \"red\", callback, { values:[\"red\",\"green\",\"blue\"]} );```\n\n  or if you want to use objects:\n\n  ```this.addWidget(\"combo\",\"Combo\", value1, callback, { values: { \"title1\":value1, \"title2\":value2 } } );```\n\n* **\"text\"** to edit a short string\n* **\"toggle\"** like a checkbox\n* **\"button\"**\n\nThe fourth optional parameter could be options for the widget, the parameters accepted are:\n* **property**: specifies the name of a property to modify when the widget changes\n* **min**: min value\n* **max**: max value\n* **precision**: set the number of digits after decimal point\n* **callback**: function to call when the value changes.\n\nWidget's value is not serialized by default when storing the node state, but if you want to store the value of widgets just set serialize_widgets to true:\n\n```js\nfunction MyNode()\n{\n  this.addWidget(\"text\",\"name\",\"\");\n  this.serialize_widgets = true;\n}\n```\n\nOr if you want to associate a widget with a property of the node, then specify it in the options:\n\n```js\nfunction MyNode()\n{\n  this.properties = { surname: \"smith\" };\n  this.addWidget(\"text\",\"Surname\",\"\", { property: \"surname\"}); //this will modify the node.properties\n}\n```\n## LGraphCanvas\nLGraphCanvas is the class in charge of rendering/interaction with the nodes inside the browser.\n\n## LGraphCanvas settings\nThere are graph canvas settings that could be defined or modified to change behaviour:\n\n* **allow_interaction**: when set to `false` disable interaction with the canvas (`flags.allow_interaction` on node can be used to override graph canvas setting)\n\n### Canvas Shortcuts\n* Space - Holding space key while moving the cursor moves the canvas around. It works when holding the mouse button down so it is easier to connect different nodes when the canvas gets too large.\n* Ctrl/Shift + Click - Add clicked node to selection.\n* Ctrl + A - Select all nodes\n* Ctrl + C/Ctrl + V - Copy and paste selected nodes, without maintaining the connection to the outputs of unselected nodes.\n* Ctrl + C/Ctrl + Shift + V - Copy and paste selected nodes, and maintaining the connection from the outputs of unselected nodes to the inputs of the newly pasted nodes.\n* Holding Shift and drag selected nodes - Move multiple selected nodes at the same time.\n\n# Execution Flow\nTo execute a graph you must call ```graph.runStep()```.\n\nThis function will call the method ```node.onExecute()``` for every node in the graph.\n\nThe order of execution is determined by the system according to the morphology of the graph (nodes without inputs are considered level 0, then nodes connected to nodes of level 0 are level 1, and so on). This order is computed only when the graph morphology changes (new nodes are created, connections change).\n\nIt is up to the developer to decide how to handle inputs and outputs from inside the node.\n\nThe data send through outputs using ```this.setOutputData(0,data)``` is stored in the link, so if the node connected through that link does ```this.getInputData(0)``` it will receive the same data sent.\n\nFor rendering, the nodes are executed according to their order in the ```graph._nodes``` array, which changes when the user interact with the GraphCanvas (clicked nodes are moved to the back of the array so they are rendered the last).\n\n\n## Integration\n\nTo integrate in you HTML application:\n\n```js\nvar graph = new LiteGraph.LGraph();\nvar graph_canvas = new LiteGraph.LGraphCanvas( canvas, graph );\n```\n\nIf you want to start the graph then:\n```js\ngraph.start();\n```\n\n## Events\n\nWhen we run a step in a graph (using ```graph.runStep()```) every node onExecute method will be called.\nBut sometimes you want that actions are only performed when some trigger is activated, for this situations you can use Events.\n\nEvents allow to trigger executions in nodes only when an event is dispatched from one node.\n\nTo define slots for nodes you must use the type LiteGraph.ACTION for inputs, and LIteGraph.EVENT for outputs:\n\n```js\nfunction MyNode()\n{\n  this.addInput(\"play\", LiteGraph.ACTION );\n  this.addInput(\"onFinish\", LiteGraph.EVENT );\n}\n```\n\nNow to execute some code when an event is received from an input, you must define the method onAction:\n\n```js\nMyNode.prototype.onAction = function(action, data)\n{\n   if(action == \"play\")\n   {\n     //do your action...\n   }\n\n}\n```\n\nAnd the last thing is to trigger events when something in your node happens. You could trigger them from inside the onExecute or from any other interaction:\n\n```js\nMyNode.prototype.onAction = function(action, data)\n{\n   if( this.button_was_clicked )\n    this.triggerSlot(0); //triggers event in slot 0\n}\n```\n\nThere are some nodes already available to handle events, like delaying, counting, etc.\n\n\n### Customising Link Tooltips\n\nWhen hovering over a link that connects two nodes together, a tooltip will be shown allowing the user to see the data that is being output from one node to the other.\n\nSometimes, you may have a node that outputs an object, rather than a primitive value that can be easily represented (like a string). In these instances, the tooltip will default to showing `[Object]`.\n\nIf you need a more descriptive tooltip, you can achieve this by adding a `toToolTip` function to your object which returns the text you wish to display in the tooltip.\n\nFor example, to ensure the link from output slot 0 shows `A useful description`, the output object would look like this:\n\n```javascript\nthis.setOutputData(0, {\n  complexObject: {\n    yes: true,\n  },\n  toToolTip: () => 'A useful description',\n});\n```\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" />\r\n<!--\r\n<meta property=\"og:title\" content=\"GameEditor\" />\r\n<meta property=\"og:description\" content=\"GameEditor for simple games\" />\r\n<meta property=\"og:image\" content=\"\" />\r\n-->\r\n\r\n<title>litegraph.js</title>\r\n\t<link type=\"text/css\" rel=\"stylesheet\" media=\"all\" href=\"https://github.com/assets/github-c066013be20516a6aae45467044fa109f18bd0c6.css\">\r\n\t<link type=\"text/css\" rel=\"stylesheet\" media=\"all\" href=\"https://github.com/assets/github2-97081caeb5890bc349a9ff52de33ac5ba1a4b6cc.css\">\r\n\t<link type=\"text/css\" rel=\"stylesheet\" media=\"all\" href=\"https://github.com/assets/styleguide-4a2ef42768c9e616cf8022dec2fe99290430ac2c.css\">\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />\r\n\t<style type='text/css'>\r\n\t</style>\r\n</head>\r\n<body>\r\n\r\n<div id=\"wrap\">\r\n\t<div id=\"main\">\r\n\t\t<div id=\"content\" class=\"markdown-body\">\r\n\t\t\t<h1>litegraph.js</h1>\r\n\t\t\t<p>Litegraph.js is a library that allows to create modular graphs on the web, similar to PureData.</p>\r\n\t\t\t<p>Graphs can be used to create workflows, image processing, audio, or any kind of network of modules interacting with each other.</p>\r\n\t\t\t<p>Some of the main features:</p>\r\n\t\t\t<ul>\r\n\t\t\t\t<li>Automatic sorting of modules according to basic rules.</li>\r\n\t\t\t\t<li>Dynamic number of input/outputs per module.</li>\r\n\t\t\t\t<li>Persistence, graphs can be serialized in a JSON.</li>\r\n\t\t\t\t<li>Optimized render in a HTML5 Canvas (supports hundres of modules on the screen).</li>\r\n\t\t\t\t<li>Allows to run the graphs without the need of the canvas (standalone mode).</li>\r\n\t\t\t\t<li>Simple API. It is very easy to create new modules.</li>\r\n\t\t\t\t<li>Edit and Live mode, (in live mode only modules with widgets are rendered.</li>\r\n\t\t\t\t<li>Contextual menu in the editor.</li>\r\n\t\t\t</ul>\r\n\t\t\t\r\n\t\t\t<h2>Usage</h2>\r\n\r\n\t\t\t<p>Include the library</p>\r\n\t\t\t<pre>\r\n&lt;script type=\"text/javascript\" src=\"../src/litegraph.js\"&gt;&lt;/script&gt;</pre>\r\n\r\n\t\t\t<p>Create a graph</p>\r\n\t\t\t<pre>\r\nvar graph = new LGraph();</pre>\r\n\r\n\t\t\t<p>Create a canvas renderer</p>\r\n\t\t\t<pre>\r\nvar canvas = new LGraphCanvas(\"#mycanvas\");</pre>\r\n\r\n\t\t\t<p>Add some nodes to the graph</p>\r\n\t\t\t<pre>\r\nvar node_const = LiteGraph.createNode(\"basic/const\");\r\nnode_const.pos = [200,200];\r\ngraph.add(node_const);\r\nnode_const.setValue(4.5);\r\n\r\nvar node_watch = LiteGraph.createNode(\"basic/watch\");\r\nnode_watch.pos = [700,200];\r\ngraph.add(node_watch);</pre>\r\n\r\n\t\t\t<p>Connect them</p>\r\n\t\t\t<pre>\r\nnode_const.connect(0, node_watch, 0 );</pre>\r\n\r\n\t\t\t<p>Run the graph</p>\r\n\t\t\t<pre>\r\ngraph.start();</pre>\r\n\r\n\t\t\t<h2>Example of editor</h2>\r\n\t\t\t<ul>\r\n\t\t\t\t<li><a href=\"editor\">Module editor</a></li>\r\n\t\t\t</ul>\r\n\r\n\t\t\t<h2>Documentation</h2>\r\n\t\t\t<p>Here you can check <a href=\"doc\">automatically generated documentation</a>.</p>\r\n\r\n\t\t</div>\r\n\t</div>\r\n</div>\r\n\r\n</body>\r\n</html>\r\n\r\n"
  },
  {
    "path": "jest.config.js",
    "content": "/*\n * For a detailed explanation regarding each configuration property, visit:\n * https://jestjs.io/docs/configuration\n */\n\nmodule.exports = {\n  // All imported modules in your tests should be mocked automatically\n  // automock: false,\n\n  // Stop running tests after `n` failures\n  // bail: 0,\n\n  // The directory where Jest should store its cached dependency information\n  // cacheDirectory: \"/tmp/jest_rs\",\n\n  // Automatically clear mock calls, instances, contexts and results before every test\n  // clearMocks: false,\n\n  // Indicates whether the coverage information should be collected while executing the test\n  collectCoverage: true,\n\n  // An array of glob patterns indicating a set of files for which coverage information should be collected\n  // collectCoverageFrom: undefined,\n\n  // The directory where Jest should output its coverage files\n  coverageDirectory: \"coverage\",\n\n  // An array of regexp pattern strings used to skip coverage collection\n  // coveragePathIgnorePatterns: [\n  //   \"/node_modules/\"\n  // ],\n\n  // Indicates which provider should be used to instrument code for coverage\n  // coverageProvider: \"babel\",\n\n  // A list of reporter names that Jest uses when writing coverage reports\n  // coverageReporters: [\n  //   \"json\",\n  //   \"text\",\n  //   \"lcov\",\n  //   \"clover\"\n  // ],\n\n  // An object that configures minimum threshold enforcement for coverage results\n  // coverageThreshold: undefined,\n\n  // A path to a custom dependency extractor\n  // dependencyExtractor: undefined,\n\n  // Make calling deprecated APIs throw helpful error messages\n  // errorOnDeprecated: false,\n\n  // The default configuration for fake timers\n  // fakeTimers: {\n  //   \"enableGlobally\": false\n  // },\n\n  // Force coverage collection from ignored files using an array of glob patterns\n  // forceCoverageMatch: [],\n\n  // A path to a module which exports an async function that is triggered once before all test suites\n  // globalSetup: undefined,\n\n  // A path to a module which exports an async function that is triggered once after all test suites\n  // globalTeardown: undefined,\n\n  // A set of global variables that need to be available in all test environments\n  // globals: {},\n\n  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.\n  // maxWorkers: \"50%\",\n\n  // An array of directory names to be searched recursively up from the requiring module's location\n  // moduleDirectories: [\n  //   \"node_modules\"\n  // ],\n\n  // An array of file extensions your modules use\n  // moduleFileExtensions: [\n  //   \"js\",\n  //   \"mjs\",\n  //   \"cjs\",\n  //   \"jsx\",\n  //   \"ts\",\n  //   \"tsx\",\n  //   \"json\",\n  //   \"node\"\n  // ],\n\n  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module\n  // moduleNameMapper: {},\n\n  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader\n  // modulePathIgnorePatterns: [],\n\n  // Activates notifications for test results\n  // notify: false,\n\n  // An enum that specifies notification mode. Requires { notify: true }\n  // notifyMode: \"failure-change\",\n\n  // A preset that is used as a base for Jest's configuration\n  // preset: undefined,\n\n  // Run tests from one or more projects\n  // projects: undefined,\n\n  // Use this configuration option to add custom reporters to Jest\n  // reporters: undefined,\n\n  // Automatically reset mock state before every test\n  // resetMocks: false,\n\n  // Reset the module registry before running each individual test\n  // resetModules: false,\n\n  // A path to a custom resolver\n  // resolver: undefined,\n\n  // Automatically restore mock state and implementation before every test\n  // restoreMocks: false,\n\n  // The root directory that Jest should scan for tests and modules within\n  // rootDir: undefined,\n\n  // A list of paths to directories that Jest should use to search for files in\n  // roots: [\n  //   \"<rootDir>\"\n  // ],\n\n  // Allows you to use a custom runner instead of Jest's default test runner\n  // runner: \"jest-runner\",\n\n  // The paths to modules that run some code to configure or set up the testing environment before each test\n  // setupFiles: [],\n\n  // A list of paths to modules that run some code to configure or set up the testing framework before each test\n  // setupFilesAfterEnv: [],\n\n  // The number of seconds after which a test is considered as slow and reported as such in the results.\n  // slowTestThreshold: 5,\n\n  // A list of paths to snapshot serializer modules Jest should use for snapshot testing\n  // snapshotSerializers: [],\n\n  // The test environment that will be used for testing\n  // testEnvironment: \"jest-environment-node\",\n\n  // Options that will be passed to the testEnvironment\n  // testEnvironmentOptions: {},\n\n  // Adds a location field to test results\n  // testLocationInResults: false,\n\n  // The glob patterns Jest uses to detect test files\n  // testMatch: [\n  //   \"**/__tests__/**/*.[jt]s?(x)\",\n  //   \"**/?(*.)+(spec|test).[tj]s?(x)\"\n  // ],\n\n  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped\n  // testPathIgnorePatterns: [\n  //   \"/node_modules/\"\n  // ],\n\n  // The regexp pattern or array of patterns that Jest uses to detect test files\n  // testRegex: [],\n\n  // This option allows the use of a custom results processor\n  // testResultsProcessor: undefined,\n\n  // This option allows use of a custom test runner\n  // testRunner: \"jest-circus/runner\",\n\n  // A map from regular expressions to paths to transformers\n  // transform: undefined,\n\n  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation\n  // transformIgnorePatterns: [\n  //   \"/node_modules/\",\n  //   \"\\\\.pnp\\\\.[^\\\\/]+$\"\n  // ],\n\n  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them\n  // unmockedModulePathPatterns: undefined,\n\n  // Indicates whether each individual test should be reported during the run\n  // verbose: undefined,\n\n  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode\n  // watchPathIgnorePatterns: [],\n\n  // Whether to use watchman for file crawling\n  // watchman: true,\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"litegraph.js\",\n    \"version\": \"0.7.14\",\n    \"description\": \"A graph node editor similar to PD or UDK Blueprints. It works in an HTML5 Canvas and allows to export graphs to be included in applications.\",\n    \"main\": \"build/litegraph.js\",\n    \"types\": \"src/litegraph.d.ts\",\n    \"directories\": {\n        \"doc\": \"doc\"\n    },\n    \"private\": false,\n    \"scripts\": {\n        \"prebuild\": \"rimraf build\",\n        \"build\": \"grunt build\",\n        \"start\": \"nodemon utils/server.js\",\n        \"test\": \"jest\",\n        \"test:allVersions\": \"./utils/test.sh\",\n        \"prettier\": \"npx prettier --write src/**/*.* css/**/*.*\",\n        \"lint\": \"npx eslint src\",\n        \"lint:fix\": \"npx eslint --fix src\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git+https://github.com/jagenjo/litegraph.js.git\"\n    },\n    \"author\": \"jagenjo\",\n    \"license\": \"MIT\",\n    \"files\": [\n        \"build\",\n        \"css/litegraph.css\",\n        \"src/litegraph.d.ts\"\n    ],\n    \"bugs\": {\n        \"url\": \"https://github.com/jagenjo/litegraph.js/issues\"\n    },\n    \"homepage\": \"https://github.com/jagenjo/litegraph.js#readme\",\n    \"devDependencies\": {\n        \"@types/jest\": \"^28.1.3\",\n        \"eslint\": \"^8.37.0 \",\n        \"eslint-plugin-jest\": \"^27.2.1\",\n        \"express\": \"^4.17.1\",\n        \"google-closure-compiler\": \"^20230411.0.0\",\n        \"grunt\": \"^1.1.0\",\n        \"grunt-cli\": \"^1.2.0\",\n        \"grunt-closure-tools\": \"^1.0.0\",\n        \"grunt-contrib-concat\": \"^2.1.0\",\n        \"jest\": \"^28.1.3\",\n        \"jest-cli\": \"^28.1.3\",\n        \"nodemon\": \"^2.0.22\",\n        \"rimraf\": \"^5.0.0\",\n        \"yuidocjs\": \"^0.10.2\"\n    }\n}\n"
  },
  {
    "path": "src/litegraph-editor.js",
    "content": "//Creates an interface to access extra features from a graph (like play, stop, live, etc)\r\nfunction Editor(container_id, options) {\r\n    options = options || {};\r\n\r\n    //fill container\r\n    var html = \"<div class='header'><div class='tools tools-left'></div><div class='tools tools-right'></div></div>\";\r\n    html += \"<div class='content'><div class='editor-area'><canvas class='graphcanvas' width='1000' height='500' tabindex=10></canvas></div></div>\";\r\n    html += \"<div class='footer'><div class='tools tools-left'></div><div class='tools tools-right'></div></div>\";\r\n\r\n    var root = document.createElement(\"div\");\r\n    this.root = root;\r\n    root.className = \"litegraph litegraph-editor\";\r\n    root.innerHTML = html;\r\n\r\n    this.tools = root.querySelector(\".tools\");\r\n    this.content = root.querySelector(\".content\");\r\n    this.footer = root.querySelector(\".footer\");\r\n\r\n    var canvas = this.canvas = root.querySelector(\".graphcanvas\");\r\n\r\n    //create graph\r\n    var graph = (this.graph = new LGraph());\r\n    var graphcanvas = this.graphcanvas = new LGraphCanvas(canvas, graph);\r\n    graphcanvas.background_image = \"imgs/grid.png\";\r\n    graph.onAfterExecute = function() {\r\n        graphcanvas.draw(true);\r\n    };\r\n\r\n\tgraphcanvas.onDropItem = this.onDropItem.bind(this);\r\n\r\n    //add stuff\r\n    //this.addToolsButton(\"loadsession_button\",\"Load\",\"imgs/icon-load.png\", this.onLoadButton.bind(this), \".tools-left\" );\r\n    //this.addToolsButton(\"savesession_button\",\"Save\",\"imgs/icon-save.png\", this.onSaveButton.bind(this), \".tools-left\" );\r\n    this.addLoadCounter();\r\n    this.addToolsButton(\r\n        \"playnode_button\",\r\n        \"Play\",\r\n        \"imgs/icon-play.png\",\r\n        this.onPlayButton.bind(this),\r\n        \".tools-right\"\r\n    );\r\n    this.addToolsButton(\r\n        \"playstepnode_button\",\r\n        \"Step\",\r\n        \"imgs/icon-playstep.png\",\r\n        this.onPlayStepButton.bind(this),\r\n        \".tools-right\"\r\n    );\r\n\r\n    if (!options.skip_livemode) {\r\n        this.addToolsButton(\r\n            \"livemode_button\",\r\n            \"Live\",\r\n            \"imgs/icon-record.png\",\r\n            this.onLiveButton.bind(this),\r\n            \".tools-right\"\r\n        );\r\n    }\r\n    if (!options.skip_maximize) {\r\n        this.addToolsButton(\r\n            \"maximize_button\",\r\n            \"\",\r\n            \"imgs/icon-maximize.png\",\r\n            this.onFullscreenButton.bind(this),\r\n            \".tools-right\"\r\n        );\r\n    }\r\n    if (options.miniwindow) {\r\n        this.addMiniWindow(300, 200);\r\n    }\r\n\r\n    //append to DOM\r\n    var parent = document.getElementById(container_id);\r\n    if (parent) {\r\n        parent.appendChild(root);\r\n    }\r\n\r\n    graphcanvas.resize();\r\n    //graphcanvas.draw(true,true);\r\n}\r\n\r\nEditor.prototype.addLoadCounter = function() {\r\n    var meter = document.createElement(\"div\");\r\n    meter.className = \"headerpanel loadmeter toolbar-widget\";\r\n\r\n    var html =\r\n        \"<div class='cpuload'><strong>CPU</strong> <div class='bgload'><div class='fgload'></div></div></div>\";\r\n    html +=\r\n        \"<div class='gpuload'><strong>GFX</strong> <div class='bgload'><div class='fgload'></div></div></div>\";\r\n\r\n    meter.innerHTML = html;\r\n    this.root.querySelector(\".header .tools-left\").appendChild(meter);\r\n    var self = this;\r\n\r\n    setInterval(function() {\r\n        meter.querySelector(\".cpuload .fgload\").style.width =\r\n            2 * self.graph.execution_time * 90 + \"px\";\r\n        if (self.graph.status == LGraph.STATUS_RUNNING) {\r\n            meter.querySelector(\".gpuload .fgload\").style.width =\r\n                self.graphcanvas.render_time * 10 * 90 + \"px\";\r\n        } else {\r\n            meter.querySelector(\".gpuload .fgload\").style.width = 4 + \"px\";\r\n        }\r\n    }, 200);\r\n};\r\n\r\nEditor.prototype.addToolsButton = function( id, name, icon_url, callback, container ) {\r\n    if (!container) {\r\n        container = \".tools\";\r\n    }\r\n\r\n    var button = this.createButton(name, icon_url, callback);\r\n    button.id = id;\r\n    this.root.querySelector(container).appendChild(button);\r\n};\r\n\r\nEditor.prototype.createButton = function(name, icon_url, callback) {\r\n    var button = document.createElement(\"button\");\r\n    if (icon_url) {\r\n        button.innerHTML = \"<img src='\" + icon_url + \"'/> \";\r\n    }\r\n\tbutton.classList.add(\"btn\");\r\n    button.innerHTML += name;\r\n\tif(callback)\r\n\t\tbutton.addEventListener(\"click\", callback );\r\n    return button;\r\n};\r\n\r\nEditor.prototype.onLoadButton = function() {\r\n    var panel = this.graphcanvas.createPanel(\"Load session\",{closable:true});\r\n\t//TO DO\r\n\r\n    this.root.appendChild(panel);\r\n};\r\n\r\nEditor.prototype.onSaveButton = function() {};\r\n\r\nEditor.prototype.onPlayButton = function() {\r\n    var graph = this.graph;\r\n    var button = this.root.querySelector(\"#playnode_button\");\r\n\r\n    if (graph.status == LGraph.STATUS_STOPPED) {\r\n        button.innerHTML = \"<img src='imgs/icon-stop.png'/> Stop\";\r\n        graph.start();\r\n    } else {\r\n        button.innerHTML = \"<img src='imgs/icon-play.png'/> Play\";\r\n        graph.stop();\r\n    }\r\n};\r\n\r\nEditor.prototype.onPlayStepButton = function() {\r\n    var graph = this.graph;\r\n    graph.runStep(1);\r\n    this.graphcanvas.draw(true, true);\r\n};\r\n\r\nEditor.prototype.onLiveButton = function() {\r\n    var is_live_mode = !this.graphcanvas.live_mode;\r\n    this.graphcanvas.switchLiveMode(true);\r\n    this.graphcanvas.draw();\r\n    var url = this.graphcanvas.live_mode\r\n        ? \"imgs/gauss_bg_medium.jpg\"\r\n        : \"imgs/gauss_bg.jpg\";\r\n    var button = this.root.querySelector(\"#livemode_button\");\r\n    button.innerHTML = !is_live_mode\r\n        ? \"<img src='imgs/icon-record.png'/> Live\"\r\n        : \"<img src='imgs/icon-gear.png'/> Edit\";\r\n};\r\n\r\nEditor.prototype.onDropItem = function(e)\r\n{\r\n\tvar that = this;\r\n\tfor(var i = 0; i < e.dataTransfer.files.length; ++i)\r\n\t{\r\n\t\tvar file = e.dataTransfer.files[i];\r\n\t\tvar ext = LGraphCanvas.getFileExtension(file.name);\r\n\t\tvar reader = new FileReader();\r\n\t\tif(ext == \"json\")\r\n\t\t{\r\n\t\t\treader.onload = function(event) {\r\n\t\t\t\tvar data = JSON.parse( event.target.result );\r\n\t\t\t\tthat.graph.configure(data);\r\n\t\t\t};\r\n\t\t\treader.readAsText(file);\r\n\t\t}\r\n\t}\r\n}\r\n\r\nEditor.prototype.goFullscreen = function() {\r\n    if (this.root.requestFullscreen) {\r\n        this.root.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT);\r\n    } else if (this.root.mozRequestFullscreen) {\r\n        this.root.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT);\r\n    } else if (this.root.webkitRequestFullscreen) {\r\n        this.root.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);\r\n    } else {\r\n        throw \"Fullscreen not supported\";\r\n    }\r\n\r\n    var self = this;\r\n    setTimeout(function() {\r\n        self.graphcanvas.resize();\r\n    }, 100);\r\n};\r\n\r\nEditor.prototype.onFullscreenButton = function() {\r\n    this.goFullscreen();\r\n};\r\n\r\nEditor.prototype.addMiniWindow = function(w, h) {\r\n    var miniwindow = document.createElement(\"div\");\r\n    miniwindow.className = \"litegraph miniwindow\";\r\n    miniwindow.innerHTML =\r\n        \"<canvas class='graphcanvas' width='\" +\r\n        w +\r\n        \"' height='\" +\r\n        h +\r\n        \"' tabindex=10></canvas>\";\r\n    var canvas = miniwindow.querySelector(\"canvas\");\r\n    var that = this;\r\n\r\n    var graphcanvas = new LGraphCanvas( canvas, this.graph );\r\n    graphcanvas.show_info = false;\r\n    graphcanvas.background_image = \"imgs/grid.png\";\r\n    graphcanvas.scale = 0.25;\r\n    graphcanvas.allow_dragnodes = false;\r\n    graphcanvas.allow_interaction = false;\r\n    graphcanvas.render_shadows = false;\r\n    graphcanvas.max_zoom = 0.25;\r\n    this.miniwindow_graphcanvas = graphcanvas;\r\n    graphcanvas.onClear = function() {\r\n        graphcanvas.scale = 0.25;\r\n        graphcanvas.allow_dragnodes = false;\r\n        graphcanvas.allow_interaction = false;\r\n    };\r\n    graphcanvas.onRenderBackground = function(canvas, ctx) {\r\n        ctx.strokeStyle = \"#567\";\r\n        var tl = that.graphcanvas.convertOffsetToCanvas([0, 0]);\r\n        var br = that.graphcanvas.convertOffsetToCanvas([\r\n            that.graphcanvas.canvas.width,\r\n            that.graphcanvas.canvas.height\r\n        ]);\r\n        tl = this.convertCanvasToOffset(tl);\r\n        br = this.convertCanvasToOffset(br);\r\n        ctx.lineWidth = 1;\r\n        ctx.strokeRect(\r\n            Math.floor(tl[0]) + 0.5,\r\n            Math.floor(tl[1]) + 0.5,\r\n            Math.floor(br[0] - tl[0]),\r\n            Math.floor(br[1] - tl[1])\r\n        );\r\n    };\r\n\r\n    miniwindow.style.position = \"absolute\";\r\n    miniwindow.style.top = \"4px\";\r\n    miniwindow.style.right = \"4px\";\r\n\r\n    var close_button = document.createElement(\"div\");\r\n    close_button.className = \"corner-button\";\r\n    close_button.innerHTML = \"&#10060;\";\r\n    close_button.addEventListener(\"click\", function(e) {\r\n        graphcanvas.setGraph(null);\r\n        miniwindow.parentNode.removeChild(miniwindow);\r\n    });\r\n    miniwindow.appendChild(close_button);\r\n\r\n    this.root.querySelector(\".content\").appendChild(miniwindow);\r\n};\r\n\r\nEditor.prototype.addMultiview = function()\r\n{\r\n\tvar canvas = this.canvas;\r\n\tthis.graphcanvas.ctx.fillStyle = \"black\";\r\n\tthis.graphcanvas.ctx.fillRect(0,0,canvas.width,canvas.height);\r\n\tthis.graphcanvas.viewport = [0,0,canvas.width*0.5-2,canvas.height];\r\n\r\n\tvar graphcanvas = new LGraphCanvas( canvas, this.graph );\r\n    graphcanvas.background_image = \"imgs/grid.png\";\r\n    this.graphcanvas2 = graphcanvas;\r\n\tthis.graphcanvas2.viewport = [canvas.width*0.5,0,canvas.width*0.5,canvas.height];\r\n}\r\n\r\nLiteGraph.Editor = Editor;\r\n"
  },
  {
    "path": "src/litegraph.d.ts",
    "content": "// Type definitions for litegraph.js 0.7.0\n// Project: litegraph.js\n// Definitions by: NateScarlet <https://github.com/NateScarlet>\n\nexport type Vector2 = [number, number];\nexport type Vector4 = [number, number, number, number];\nexport type widgetTypes =\n    | \"number\"\n    | \"slider\"\n    | \"combo\"\n    | \"text\"\n    | \"toggle\"\n    | \"button\";\nexport type SlotShape =\n    | typeof LiteGraph.BOX_SHAPE\n    | typeof LiteGraph.CIRCLE_SHAPE\n    | typeof LiteGraph.ARROW_SHAPE\n    | typeof LiteGraph.SQUARE_SHAPE\n    | number; // For custom shapes\n\n/** https://github.com/jagenjo/litegraph.js/tree/master/guides#node-slots */\nexport interface INodeSlot {\n    name: string;\n    type: string | -1;\n    label?: string;\n    dir?:\n        | typeof LiteGraph.UP\n        | typeof LiteGraph.RIGHT\n        | typeof LiteGraph.DOWN\n        | typeof LiteGraph.LEFT;\n    color_on?: string;\n    color_off?: string;\n    shape?: SlotShape;\n    locked?: boolean;\n    nameLocked?: boolean;\n}\n\nexport interface INodeInputSlot extends INodeSlot {\n    link: LLink[\"id\"] | null;\n}\nexport interface INodeOutputSlot extends INodeSlot {\n    links: LLink[\"id\"][] | null;\n}\n\nexport type WidgetCallback<T extends IWidget = IWidget> = (\n    this: T,\n    value: T[\"value\"],\n    graphCanvas: LGraphCanvas,\n    node: LGraphNode,\n    pos: Vector2,\n    event?: MouseEvent\n) => void;\n\nexport interface IWidget<TValue = any, TOptions = any> {\n    name: string | null;\n    value: TValue;\n    options?: TOptions;\n    type?: widgetTypes;\n    y?: number;\n    property?: string;\n    last_y?: number;\n    clicked?: boolean;\n    marker?: boolean;\n    callback?: WidgetCallback<this>;\n    /** Called by `LGraphCanvas.drawNodeWidgets` */\n    draw?(\n        ctx: CanvasRenderingContext2D,\n        node: LGraphNode,\n        width: number,\n        posY: number,\n        height: number\n    ): void;\n    /**\n     * Called by `LGraphCanvas.processNodeWidgets`\n     * https://github.com/jagenjo/litegraph.js/issues/76\n     */\n    mouse?(\n        event: MouseEvent,\n        pos: Vector2,\n        node: LGraphNode\n    ): boolean;\n    /** Called by `LGraphNode.computeSize` */\n    computeSize?(width: number): [number, number];\n}\nexport interface IButtonWidget extends IWidget<null, {}> {\n    type: \"button\";\n}\nexport interface IToggleWidget\n    extends IWidget<boolean, { on?: string; off?: string }> {\n    type: \"toggle\";\n}\nexport interface ISliderWidget\n    extends IWidget<number, { max: number; min: number }> {\n    type: \"slider\";\n}\nexport interface INumberWidget extends IWidget<number, { precision: number }> {\n    type: \"number\";\n}\nexport interface IComboWidget\n    extends IWidget<\n        string[],\n        {\n            values:\n                | string[]\n                | ((widget: IComboWidget, node: LGraphNode) => string[]);\n        }\n    > {\n    type: \"combo\";\n}\n\nexport interface ITextWidget extends IWidget<string, {}> {\n    type: \"text\";\n}\n\nexport interface IContextMenuItem {\n    content: string;\n    callback?: ContextMenuEventListener;\n    /** Used as innerHTML for extra child element */\n    title?: string;\n    disabled?: boolean;\n    has_submenu?: boolean;\n    submenu?: {\n        options: ContextMenuItem[];\n    } & IContextMenuOptions;\n    className?: string;\n}\nexport interface IContextMenuOptions {\n    callback?: ContextMenuEventListener;\n    ignore_item_callbacks?: Boolean;\n    event?: MouseEvent | CustomEvent;\n    parentMenu?: ContextMenu;\n    autoopen?: boolean;\n    title?: string;\n    extra?: any;\n}\n\nexport type ContextMenuItem = IContextMenuItem | null;\nexport type ContextMenuEventListener = (\n    value: ContextMenuItem,\n    options: IContextMenuOptions,\n    event: MouseEvent,\n    parentMenu: ContextMenu | undefined,\n    node: LGraphNode\n) => boolean | void;\n\nexport const LiteGraph: {\n    VERSION: number;\n\n    CANVAS_GRID_SIZE: number;\n\n    NODE_TITLE_HEIGHT: number;\n    NODE_TITLE_TEXT_Y: number;\n    NODE_SLOT_HEIGHT: number;\n    NODE_WIDGET_HEIGHT: number;\n    NODE_WIDTH: number;\n    NODE_MIN_WIDTH: number;\n    NODE_COLLAPSED_RADIUS: number;\n    NODE_COLLAPSED_WIDTH: number;\n    NODE_TITLE_COLOR: string;\n    NODE_TEXT_SIZE: number;\n    NODE_TEXT_COLOR: string;\n    NODE_SUBTEXT_SIZE: number;\n    NODE_DEFAULT_COLOR: string;\n    NODE_DEFAULT_BGCOLOR: string;\n    NODE_DEFAULT_BOXCOLOR: string;\n    NODE_DEFAULT_SHAPE: string;\n    DEFAULT_SHADOW_COLOR: string;\n    DEFAULT_GROUP_FONT: number;\n\n    LINK_COLOR: string;\n    EVENT_LINK_COLOR: string;\n    CONNECTING_LINK_COLOR: string;\n\n    MAX_NUMBER_OF_NODES: number; //avoid infinite loops\n    DEFAULT_POSITION: Vector2; //default node position\n    VALID_SHAPES: [\"default\", \"box\", \"round\", \"card\"]; //,\"circle\"\n\n    //shapes are used for nodes but also for slots\n    BOX_SHAPE: 1;\n    ROUND_SHAPE: 2;\n    CIRCLE_SHAPE: 3;\n    CARD_SHAPE: 4;\n    ARROW_SHAPE: 5;\n    SQUARE_SHAPE: 6;\n\n    //enums\n    INPUT: 1;\n    OUTPUT: 2;\n\n    EVENT: -1; //for outputs\n    ACTION: -1; //for inputs\n\n    ALWAYS: 0;\n    ON_EVENT: 1;\n    NEVER: 2;\n    ON_TRIGGER: 3;\n\n    UP: 1;\n    DOWN: 2;\n    LEFT: 3;\n    RIGHT: 4;\n    CENTER: 5;\n\n    STRAIGHT_LINK: 0;\n    LINEAR_LINK: 1;\n    SPLINE_LINK: 2;\n\n    NORMAL_TITLE: 0;\n    NO_TITLE: 1;\n    TRANSPARENT_TITLE: 2;\n    AUTOHIDE_TITLE: 3;\n\n    node_images_path: string;\n\n    debug: boolean;\n    catch_exceptions: boolean;\n    throw_errors: boolean;\n    /** if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits */\n    allow_scripts: boolean;\n    /** node types by string */\n    registered_node_types: Record<string, LGraphNodeConstructor>;\n    /** used for dropping files in the canvas */\n    node_types_by_file_extension: Record<string, LGraphNodeConstructor>;\n    /** node types by class name */\n    Nodes: Record<string, LGraphNodeConstructor>;\n\n    /** used to add extra features to the search box */\n    searchbox_extras: Record<\n        string,\n        {\n            data: { outputs: string[][]; title: string };\n            desc: string;\n            type: string;\n        }\n    >;\n\n    createNode<T extends LGraphNode = LGraphNode>(type: string): T;\n    /** Register a node class so it can be listed when the user wants to create a new one */\n    registerNodeType(type: string, base: { new (): LGraphNode }): void;\n    /** removes a node type from the system */\n    unregisterNodeType(type: string): void;\n    /** Removes all previously registered node's types. */\n    clearRegisteredTypes(): void;\n    /**\n     * Create a new node type by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function.\n     * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output.\n     * @param name node name with namespace (p.e.: 'math/sum')\n     * @param func\n     * @param param_types an array containing the type of every parameter, otherwise parameters will accept any type\n     * @param return_type string with the return type, otherwise it will be generic\n     * @param properties properties to be configurable\n     * @return {LGraphNode}\n     */\n    wrapFunctionAsNode(\n        name: string,\n        func: (...args: any[]) => any,\n        param_types?: string[],\n        return_type?: string,\n        properties?: object\n    ): LGraphNode;\n\n    /**\n     * Adds this method to all node types, existing and to be created\n     * (You can add it to LGraphNode.prototype but then existing node types wont have it)\n     */\n    addNodeMethod(name: string, func: (...args: any[]) => any): void;\n\n    /**\n     * Create a node of a given type with a name. The node is not attached to any graph yet.\n     * @param type full name of the node class. p.e. \"math/sin\"\n     * @param name a name to distinguish from other nodes\n     * @param options to set options\n     */\n    createNode<T extends LGraphNode>(\n        type: string,\n        title: string,\n        options: object\n    ): T;\n\n    /**\n     * Returns a registered node type with a given name\n     * @param type full name of the node class. p.e. \"math/sin\"\n     */\n    getNodeType<T extends LGraphNode>(type: string): LGraphNodeConstructor<T>;\n\n    /**\n     * Returns a list of node types matching one category\n     * @method getNodeTypesInCategory\n     * @param {String} category category name\n     * @param {String} filter only nodes with ctor.filter equal can be shown\n     * @return {Array} array with all the node classes\n     */\n    getNodeTypesInCategory(\n        category: string,\n        filter: string\n    ): LGraphNodeConstructor[];\n\n    /**\n     * Returns a list with all the node type categories\n     * @method getNodeTypesCategories\n     * @param {String} filter only nodes with ctor.filter equal can be shown\n     * @return {Array} array with all the names of the categories\n     */                           \n    getNodeTypesCategories(filter: string): string[];\n\n    /** debug purposes: reloads all the js scripts that matches a wildcard */\n    reloadNodes(folder_wildcard: string): void;\n\n    getTime(): number;\n    LLink: typeof LLink;\n    LGraph: typeof LGraph;\n    DragAndScale: typeof DragAndScale;\n    compareObjects(a: object, b: object): boolean;\n    distance(a: Vector2, b: Vector2): number;\n    colorToString(c: string): string;\n    isInsideRectangle(\n        x: number,\n        y: number,\n        left: number,\n        top: number,\n        width: number,\n        height: number\n    ): boolean;\n    growBounding(bounding: Vector4, x: number, y: number): Vector4;\n    isInsideBounding(p: Vector2, bb: Vector4): boolean;\n    hex2num(hex: string): [number, number, number];\n    num2hex(triplet: [number, number, number]): string;\n    ContextMenu: typeof ContextMenu;\n    extendClass<A, B>(target: A, origin: B): A & B;\n    getParameterNames(func: string): string[];\n};\n\nexport type serializedLGraph<\n    TNode = ReturnType<LGraphNode[\"serialize\"]>,\n    // https://github.com/jagenjo/litegraph.js/issues/74\n    TLink = [number, number, number, number, number, string],\n    TGroup = ReturnType<LGraphGroup[\"serialize\"]>\n> = {\n    last_node_id: LGraph[\"last_node_id\"];\n    last_link_id: LGraph[\"last_link_id\"];\n    nodes: TNode[];\n    links: TLink[];\n    groups: TGroup[];\n    config: LGraph[\"config\"];\n    version: typeof LiteGraph.VERSION;\n};\n\nexport declare class LGraph {\n    static supported_types: string[];\n    static STATUS_STOPPED: 1;\n    static STATUS_RUNNING: 2;\n\n    constructor(o?: object);\n\n    filter: string;\n    catch_errors: boolean;\n    /** custom data */\n    config: object;\n    elapsed_time: number;\n    fixedtime: number;\n    fixedtime_lapse: number;\n    globaltime: number;\n    inputs: any;\n    iteration: number;\n    last_link_id: number;\n    last_node_id: number;\n    last_update_time: number;\n    links: Record<number, LLink>;\n    list_of_graphcanvas: LGraphCanvas[];\n    outputs: any;\n    runningtime: number;\n    starttime: number;\n    status: typeof LGraph.STATUS_RUNNING | typeof LGraph.STATUS_STOPPED;\n\n    private _nodes: LGraphNode[];\n    private _groups: LGraphGroup[];\n    private _nodes_by_id: Record<number, LGraphNode>;\n    /** nodes that are executable sorted in execution order */\n    private _nodes_executable:\n        | (LGraphNode & { onExecute: NonNullable<LGraphNode[\"onExecute\"]> }[])\n        | null;\n    /** nodes that contain onExecute */\n    private _nodes_in_order: LGraphNode[];\n    private _version: number;\n\n    getSupportedTypes(): string[];\n    /** Removes all nodes from this graph */\n    clear(): void;\n    /** Attach Canvas to this graph */\n    attachCanvas(graphCanvas: LGraphCanvas): void;\n    /** Detach Canvas to this graph */\n    detachCanvas(graphCanvas: LGraphCanvas): void;\n    /**\n     * Starts running this graph every interval milliseconds.\n     * @param interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate\n     */\n    start(interval?: number): void;\n    /** Stops the execution loop of the graph */\n    stop(): void;\n    /**\n     * Run N steps (cycles) of the graph\n     * @param num number of steps to run, default is 1\n     */\n    runStep(num?: number, do_not_catch_errors?: boolean): void;\n    /**\n     * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than\n     * nodes with only inputs.\n     */\n    updateExecutionOrder(): void;\n    /** This is more internal, it computes the executable nodes in order and returns it */\n    computeExecutionOrder<T = any>(only_onExecute: boolean, set_level: any): T;\n    /**\n     * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.\n     * It doesn't include the node itself\n     * @return an array with all the LGraphNodes that affect this node, in order of execution\n     */\n    getAncestors(node: LGraphNode): LGraphNode[];\n    /**\n     * Positions every node in a more readable manner\n     */\n    arrange(margin?: number,layout?: string): void;\n    /**\n     * Returns the amount of time the graph has been running in milliseconds\n     * @return number of milliseconds the graph has been running\n     */\n    getTime(): number;\n\n    /**\n     * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant\n     * @return number of milliseconds the graph has been running\n     */\n    getFixedTime(): number;\n\n    /**\n     * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct\n     * if the nodes are using graphical actions\n     * @return number of milliseconds it took the last cycle\n     */\n    getElapsedTime(): number;\n    /**\n     * Sends an event to all the nodes, useful to trigger stuff\n     * @param eventName the name of the event (function to be called)\n     * @param params parameters in array format\n     */\n    sendEventToAllNodes(eventName: string, params: any[], mode?: any): void;\n\n    sendActionToCanvas(action: any, params: any[]): void;\n    /**\n     * Adds a new node instance to this graph\n     * @param node the instance of the node\n     */\n    add(node: LGraphNode, skip_compute_order?: boolean): void;\n    /**\n     * Called when a new node is added\n     * @param node the instance of the node\n     */\n    onNodeAdded(node: LGraphNode): void;\n    /** Removes a node from the graph */\n    remove(node: LGraphNode): void;\n    /** Returns a node by its id. */\n    getNodeById(id: number): LGraphNode | undefined;\n    /**\n     * Returns a list of nodes that matches a class\n     * @param classObject the class itself (not an string)\n     * @return a list with all the nodes of this type\n     */\n    findNodesByClass<T extends LGraphNode>(\n        classObject: LGraphNodeConstructor<T>\n    ): T[];\n    /**\n     * Returns a list of nodes that matches a type\n     * @param type the name of the node type\n     * @return a list with all the nodes of this type\n     */\n    findNodesByType<T extends LGraphNode = LGraphNode>(type: string): T[];\n    /**\n     * Returns the first node that matches a name in its title\n     * @param title the name of the node to search\n     * @return the node or null\n     */\n    findNodeByTitle<T extends LGraphNode = LGraphNode>(title: string): T | null;\n    /**\n     * Returns a list of nodes that matches a name\n     * @param title the name of the node to search\n     * @return a list with all the nodes with this name\n     */\n    findNodesByTitle<T extends LGraphNode = LGraphNode>(title: string): T[];\n    /**\n     * Returns the top-most node in this position of the canvas\n     * @param x the x coordinate in canvas space\n     * @param y the y coordinate in canvas space\n     * @param nodes_list a list with all the nodes to search from, by default is all the nodes in the graph\n     * @return the node at this position or null\n     */\n    getNodeOnPos<T extends LGraphNode = LGraphNode>(\n        x: number,\n        y: number,\n        node_list?: LGraphNode[],\n        margin?: number\n    ): T | null;\n    /**\n     * Returns the top-most group in that position\n     * @param x the x coordinate in canvas space\n     * @param y the y coordinate in canvas space\n     * @return the group or null\n     */\n    getGroupOnPos(x: number, y: number): LGraphGroup | null;\n\n    onAction(action: any, param: any): void;\n    trigger(action: any, param: any): void;\n    /** Tell this graph it has a global graph input of this type */\n    addInput(name: string, type: string, value?: any): void;\n    /** Assign a data to the global graph input */\n    setInputData(name: string, data: any): void;\n    /** Returns the current value of a global graph input */\n    getInputData<T = any>(name: string): T;\n    /** Changes the name of a global graph input */\n    renameInput(old_name: string, name: string): false | undefined;\n    /** Changes the type of a global graph input */\n    changeInputType(name: string, type: string): false | undefined;\n    /** Removes a global graph input */\n    removeInput(name: string): boolean;\n    /** Creates a global graph output */\n    addOutput(name: string, type: string, value: any): void;\n    /** Assign a data to the global output */\n    setOutputData(name: string, value: string): void;\n    /** Returns the current value of a global graph output */\n    getOutputData<T = any>(name: string): T;\n\n    /** Renames a global graph output */\n    renameOutput(old_name: string, name: string): false | undefined;\n    /** Changes the type of a global graph output */\n    changeOutputType(name: string, type: string): false | undefined;\n    /** Removes a global graph output */\n    removeOutput(name: string): boolean;\n    triggerInput(name: string, value: any): void;\n    setCallback(name: string, func: (...args: any[]) => any): void;\n    beforeChange(info?: LGraphNode): void;\n    afterChange(info?: LGraphNode): void;                       \n    connectionChange(node: LGraphNode): void;\n    /** returns if the graph is in live mode */\n    isLive(): boolean;\n    /** clears the triggered slot animation in all links (stop visual animation) */\n    clearTriggeredSlots(): void;\n    /* Called when something visually changed (not the graph!) */\n    change(): void;\n    setDirtyCanvas(fg: boolean, bg: boolean): void;\n    /** Destroys a link */\n    removeLink(link_id: number): void;\n    /** Creates a Object containing all the info about this graph, it can be serialized */\n    serialize<T extends serializedLGraph>(): T;\n    /**\n     * Configure a graph from a JSON string\n     * @param data configure a graph from a JSON string\n     * @returns if there was any error parsing\n     */\n    configure(data: object, keep_old?: boolean): boolean | undefined;\n    load(url: string): void;\n}\n\nexport type SerializedLLink = [number, string, number, number, number, number];\nexport declare class LLink {\n    id: number;\n    type: string;\n    origin_id: number;\n    origin_slot: number;\n    target_id: number;\n    target_slot: number;\n    constructor(\n        id: number,\n        type: string,\n        origin_id: number,\n        origin_slot: number,\n        target_id: number,\n        target_slot: number\n    );\n    configure(o: LLink | SerializedLLink): void;\n    serialize(): SerializedLLink;\n}\n\nexport type SerializedLGraphNode<T extends LGraphNode = LGraphNode> = {\n    id: T[\"id\"];\n    type: T[\"type\"];\n    pos: T[\"pos\"];\n    size: T[\"size\"];\n    flags: T[\"flags\"];\n    mode: T[\"mode\"];\n    inputs: T[\"inputs\"];\n    outputs: T[\"outputs\"];\n    title: T[\"title\"];\n    properties: T[\"properties\"];\n    widgets_values?: IWidget[\"value\"][];\n};\n\n/** https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#lgraphnode */\nexport declare class LGraphNode {\n    static title_color: string;\n    static title: string;\n    static type: null | string;\n    static widgets_up: boolean;\n    constructor(title?: string);\n\n    title: string;\n    type: null | string;\n    size: Vector2;\n    graph: null | LGraph;\n    graph_version: number;\n    pos: Vector2;\n    is_selected: boolean;\n    mouseOver: boolean;\n\n    id: number;\n\n    //inputs available: array of inputs\n    inputs: INodeInputSlot[];\n    outputs: INodeOutputSlot[];\n    connections: any[];\n\n    //local data\n    properties: Record<string, any>;\n    properties_info: any[];\n\n    flags: Partial<{\n        collapsed: boolean\n    }>;\n\n    color: string;\n    bgcolor: string;\n    boxcolor: string;\n    shape:\n        | typeof LiteGraph.BOX_SHAPE\n        | typeof LiteGraph.ROUND_SHAPE\n        | typeof LiteGraph.CIRCLE_SHAPE\n        | typeof LiteGraph.CARD_SHAPE\n        | typeof LiteGraph.ARROW_SHAPE;\n\n    serialize_widgets: boolean;\n    skip_list: boolean;\n\n    /** Used in `LGraphCanvas.onMenuNodeMode` */\n    mode?:\n        | typeof LiteGraph.ON_EVENT\n        | typeof LiteGraph.ON_TRIGGER\n        | typeof LiteGraph.NEVER\n        | typeof LiteGraph.ALWAYS;\n\n    /** If set to true widgets do not start after the slots */\n    widgets_up: boolean;\n    /** widgets start at y distance from the top of the node */\n    widgets_start_y: number;\n    /** if you render outside the node, it will be clipped */\n    clip_area: boolean;\n    /** if set to false it wont be resizable with the mouse */\n    resizable: boolean;\n    /** slots are distributed horizontally */\n    horizontal: boolean;\n    /** if true, the node will show the bgcolor as 'red'  */\n    has_errors?: boolean;\n\n    /** configure a node from an object containing the serialized info */\n    configure(info: SerializedLGraphNode): void;\n    /** serialize the content */\n    serialize(): SerializedLGraphNode;\n    /** Creates a clone of this node  */\n    clone(): this;\n    /** serialize and stringify */\n    toString(): string;\n    /** get the title string */\n    getTitle(): string;\n    /** sets the value of a property */\n    setProperty(name: string, value: any): void;\n    /** sets the output data */\n    setOutputData(slot: number, data: any): void;\n    /** sets the output data */\n    setOutputDataType(slot: number, type: string): void;\n    /**\n     * Retrieves the input data (data traveling through the connection) from one slot\n     * @param slot\n     * @param force_update if set to true it will force the connected node of this slot to output data into this link\n     * @return data or if it is not connected returns undefined\n     */\n    getInputData<T = any>(slot: number, force_update?: boolean): T;\n    /**\n     * Retrieves the input data type (in case this supports multiple input types)\n     * @param slot\n     * @return datatype in string format\n     */\n    getInputDataType(slot: number): string;\n    /**\n     * Retrieves the input data from one slot using its name instead of slot number\n     * @param slot_name\n     * @param force_update if set to true it will force the connected node of this slot to output data into this link\n     * @return data or if it is not connected returns null\n     */\n    getInputDataByName<T = any>(slot_name: string, force_update?: boolean): T;\n    /** tells you if there is a connection in one input slot */\n    isInputConnected(slot: number): boolean;\n    /** tells you info about an input connection (which node, type, etc) */\n    getInputInfo(\n        slot: number\n    ): { link: number; name: string; type: string | 0 } | null;\n    /** Returns the link info in the connection of an input slot */\n    getInputLink(slot: number): LLink | null;\n    /** returns the node connected in the input slot */\n    getInputNode(slot: number): LGraphNode | null;\n    /** returns the value of an input with this name, otherwise checks if there is a property with that name */\n    getInputOrProperty<T = any>(name: string): T;\n    /** tells you the last output data that went in that slot */\n    getOutputData<T = any>(slot: number): T | null;\n    /** tells you info about an output connection (which node, type, etc) */\n    getOutputInfo(\n        slot: number\n    ): { name: string; type: string; links: number[] } | null;\n    /** tells you if there is a connection in one output slot */\n    isOutputConnected(slot: number): boolean;\n    /** tells you if there is any connection in the output slots */\n    isAnyOutputConnected(): boolean;\n    /** retrieves all the nodes connected to this output slot */\n    getOutputNodes(slot: number): LGraphNode[];\n    /**  Triggers an event in this node, this will trigger any output with the same name */\n    trigger(action: string, param: any): void;\n    /**\n     * Triggers an slot event in this node\n     * @param slot the index of the output slot\n     * @param param\n     * @param link_id in case you want to trigger and specific output link in a slot\n     */\n    triggerSlot(slot: number, param: any, link_id?: number): void;\n    /**\n     * clears the trigger slot animation\n     * @param slot the index of the output slot\n     * @param link_id in case you want to trigger and specific output link in a slot\n     */\n    clearTriggeredSlot(slot: number, link_id?: number): void;\n    /** changes node size and triggers callback */\n    setSize(size: Vector2): void;\n    /**\n     * add a new property to this node\n     * @param name\n     * @param default_value\n     * @param type string defining the output type (\"vec3\",\"number\",...)\n     * @param extra_info this can be used to have special properties of the property (like values, etc)\n     */\n    addProperty<T = any>(\n        name: string,\n        default_value: any,\n        type: string,\n        extra_info?: object\n    ): T;\n    /**\n     * add a new output slot to use in this node\n     * @param name\n     * @param type string defining the output type (\"vec3\",\"number\",...)\n     * @param extra_info this can be used to have special properties of an output (label, special color, position, etc)\n     */\n    addOutput(\n        name: string,\n        type: string | -1,\n        extra_info?: Partial<INodeOutputSlot>\n    ): INodeOutputSlot;\n    /**\n     * add a new output slot to use in this node\n     * @param array of triplets like [[name,type,extra_info],[...]]\n     */\n    addOutputs(\n        array: [string, string | -1, Partial<INodeOutputSlot> | undefined][]\n    ): void;\n    /** remove an existing output slot */\n    removeOutput(slot: number): void;\n    /**\n     * add a new input slot to use in this node\n     * @param name\n     * @param type string defining the input type (\"vec3\",\"number\",...), it its a generic one use 0\n     * @param extra_info this can be used to have special properties of an input (label, color, position, etc)\n     */\n    addInput(\n        name: string,\n        type: string | -1,\n        extra_info?: Partial<INodeInputSlot>\n    ): INodeInputSlot;\n    /**\n     * add several new input slots in this node\n     * @param array of triplets like [[name,type,extra_info],[...]]\n     */\n    addInputs(\n        array: [string, string | -1, Partial<INodeInputSlot> | undefined][]\n    ): void;\n    /** remove an existing input slot */\n    removeInput(slot: number): void;\n    /**\n     * add an special connection to this node (used for special kinds of graphs)\n     * @param name\n     * @param type string defining the input type (\"vec3\",\"number\",...)\n     * @param pos position of the connection inside the node\n     * @param direction if is input or output\n     */\n    addConnection(\n        name: string,\n        type: string,\n        pos: Vector2,\n        direction: string\n    ): {\n        name: string;\n        type: string;\n        pos: Vector2;\n        direction: string;\n        links: null;\n    };\n    /** computes the minimum size of a node according to its inputs and output slots */\n    computeSize(minHeight?: Vector2): Vector2;\n    /** returns all the info available about a property of this node */\n    getPropertyInfo(property: string): object;\n    /**\n     * https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#node-widgets\n     * @return created widget\n     */\n    addWidget<T extends IWidget>(\n        type: T[\"type\"],\n        name: string,\n        value: T[\"value\"],\n        callback?: WidgetCallback<T> | string,\n        options?: T[\"options\"]\n    ): T;\n\n    addCustomWidget<T extends IWidget>(customWidget: T): T;\n\n    /**\n     * returns the bounding of the object, used for rendering purposes\n     * @method getBounding\n     * @param out [optional] a place to store the output, to free garbage\n     * @param compute_outer [optional] set to true to include the shadow and connection points in the bounding calculation\n     * @return the bounding box in format of [topleft_cornerx, topleft_cornery, width, height]\n     */\n    getBounding(out?: Vector4, compute_outer?: boolean): Vector4;\n    /** checks if a point is inside the shape of a node */\n    isPointInside(\n        x: number,\n        y: number,\n        margin?: number,\n        skipTitle?: boolean\n    ): boolean;\n    /** checks if a point is inside a node slot, and returns info about which slot */\n    getSlotInPosition(\n        x: number,\n        y: number\n    ): {\n        input?: INodeInputSlot;\n        output?: INodeOutputSlot;\n        slot: number;\n        link_pos: Vector2;\n    };\n    /**\n     * returns the input slot with a given name (used for dynamic slots), -1 if not found\n     * @param name the name of the slot\n     * @return the slot (-1 if not found)\n     */\n    findInputSlot(name: string): number;\n    /**\n     * returns the output slot with a given name (used for dynamic slots), -1 if not found\n     * @param name the name of the slot\n     * @return  the slot (-1 if not found)\n     */\n    findOutputSlot(name: string): number;\n    /**\n     * connect this node output to the input of another node\n     * @param slot (could be the number of the slot or the string with the name of the slot)\n     * @param  targetNode the target node\n     * @param  targetSlot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)\n     * @return {Object} the link_info is created, otherwise null\n     */\n    connect<T = any>(\n        slot: number | string,\n        targetNode: LGraphNode,\n        targetSlot: number | string\n    ): T | null;\n    /**\n     * disconnect one output to an specific node\n     * @param slot (could be the number of the slot or the string with the name of the slot)\n     * @param target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]\n     * @return if it was disconnected successfully\n     */\n    disconnectOutput(slot: number | string, targetNode?: LGraphNode): boolean;\n    /**\n     * disconnect one input\n     * @param slot (could be the number of the slot or the string with the name of the slot)\n     * @return if it was disconnected successfully\n     */\n    disconnectInput(slot: number | string): boolean;\n    /**\n     * returns the center of a connection point in canvas coords\n     * @param is_input true if if a input slot, false if it is an output\n     * @param slot (could be the number of the slot or the string with the name of the slot)\n     * @param out a place to store the output, to free garbage\n     * @return the position\n     **/\n    getConnectionPos(\n        is_input: boolean,\n        slot: number | string,\n        out?: Vector2\n    ): Vector2;\n    /** Force align to grid */\n    alignToGrid(): void;\n    /** Console output */\n    trace(msg: string): void;\n    /** Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */\n    setDirtyCanvas(fg: boolean, bg: boolean): void;\n    loadImage(url: string): void;\n    /** Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */\n    captureInput(v: any): void;\n    /** Collapse the node to make it smaller on the canvas */\n    collapse(force: boolean): void;\n    /** Forces the node to do not move or realign on Z */\n    pin(v?: boolean): void;\n    localToScreen(x: number, y: number, graphCanvas: LGraphCanvas): Vector2;\n\n    // https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-appearance\n    onDrawBackground?(\n        ctx: CanvasRenderingContext2D,\n        canvas: HTMLCanvasElement\n    ): void;\n    onDrawForeground?(\n        ctx: CanvasRenderingContext2D,\n        canvas: HTMLCanvasElement\n    ): void;\n\n    // https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-behaviour\n    onMouseDown?(\n        event: MouseEvent,\n        pos: Vector2,\n        graphCanvas: LGraphCanvas\n    ): void;\n    onMouseMove?(\n        event: MouseEvent,\n        pos: Vector2,\n        graphCanvas: LGraphCanvas\n    ): void;\n    onMouseUp?(\n        event: MouseEvent,\n        pos: Vector2,\n        graphCanvas: LGraphCanvas\n    ): void;\n    onMouseEnter?(\n        event: MouseEvent,\n        pos: Vector2,\n        graphCanvas: LGraphCanvas\n    ): void;\n    onMouseLeave?(\n        event: MouseEvent,\n        pos: Vector2,\n        graphCanvas: LGraphCanvas\n    ): void;\n    onKey?(event: KeyboardEvent, pos: Vector2, graphCanvas: LGraphCanvas): void;\n\n    /** Called by `LGraphCanvas.selectNodes` */\n    onSelected?(): void;\n    /** Called by `LGraphCanvas.deselectNode` */\n    onDeselected?(): void;\n    /** Called by `LGraph.runStep` `LGraphNode.getInputData` */\n    onExecute?(): void;\n    /** Called by `LGraph.serialize` */\n    onSerialize?(o: SerializedLGraphNode): void;\n    /** Called by `LGraph.configure` */\n    onConfigure?(o: SerializedLGraphNode): void;\n    /**\n     * when added to graph (warning: this is called BEFORE the node is configured when loading)\n     * Called by `LGraph.add`\n     */\n    onAdded?(graph: LGraph): void;\n    /**\n     * when removed from graph\n     * Called by `LGraph.remove` `LGraph.clear`\n     */\n    onRemoved?(): void;\n    /**\n     * if returns false the incoming connection will be canceled\n     * Called by `LGraph.connect`\n     * @param inputIndex target input slot number\n     * @param outputType type of output slot\n     * @param outputSlot output slot object\n     * @param outputNode node containing the output\n     * @param outputIndex index of output slot\n     */\n    onConnectInput?(\n        inputIndex: number,\n        outputType: INodeOutputSlot[\"type\"],\n        outputSlot: INodeOutputSlot,\n        outputNode: LGraphNode,\n        outputIndex: number\n    ): boolean;\n    /**\n     * if returns false the incoming connection will be canceled\n     * Called by `LGraph.connect`\n     * @param outputIndex target output slot number\n     * @param inputType type of input slot\n     * @param inputSlot input slot object\n     * @param inputNode node containing the input\n     * @param inputIndex index of input slot\n     */\n    onConnectOutput?(\n        outputIndex: number,\n        inputType: INodeInputSlot[\"type\"],\n        inputSlot: INodeInputSlot,\n        inputNode: LGraphNode,\n        inputIndex: number\n    ): boolean;\n\n    /**\n     * Called just before connection (or disconnect - if input is linked).\n     * A convenient place to switch to another input, or create new one.\n     * This allow for ability to automatically add slots if needed\n     * @param inputIndex\n     * @return selected input slot index, can differ from parameter value\n     */\n    onBeforeConnectInput?(\n        inputIndex: number\n    ): number;\n    \n    /** a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info or output_info ) */\n    onConnectionsChange(\n        type: number,\n        slotIndex: number,\n        isConnected: boolean,\n        link: LLink,\n        ioSlot: (INodeOutputSlot | INodeInputSlot)\n    ): void;                           \n\n    /**\n     * if returns false, will abort the `LGraphNode.setProperty`\n     * Called when a property is changed\n     * @param property\n     * @param value\n     * @param prevValue\n     */\n    onPropertyChanged?(property: string, value: any, prevValue: any): void | boolean;\n\n    /** Called by `LGraphCanvas.processContextMenu` */\n    getMenuOptions?(graphCanvas: LGraphCanvas): ContextMenuItem[];\n    getSlotMenuOptions?(slot: INodeSlot): ContextMenuItem[];\n}\n\nexport type LGraphNodeConstructor<T extends LGraphNode = LGraphNode> = {\n    new (): T;\n};\n\nexport type SerializedLGraphGroup = {\n    title: LGraphGroup[\"title\"];\n    bounding: LGraphGroup[\"_bounding\"];\n    color: LGraphGroup[\"color\"];\n    font: LGraphGroup[\"font\"];\n};\nexport declare class LGraphGroup {\n    title: string;\n    private _bounding: Vector4;\n    color: string;\n    font: string;\n\n    configure(o: SerializedLGraphGroup): void;\n    serialize(): SerializedLGraphGroup;\n    move(deltaX: number, deltaY: number, ignoreNodes?: boolean): void;\n    recomputeInsideNodes(): void;\n    isPointInside: LGraphNode[\"isPointInside\"];\n    setDirtyCanvas: LGraphNode[\"setDirtyCanvas\"];\n}\n\nexport declare class DragAndScale {\n    constructor(element?: HTMLElement, skipEvents?: boolean);\n    offset: [number, number];\n    scale: number;\n    max_scale: number;\n    min_scale: number;\n    onredraw: Function | null;\n    enabled: boolean;\n    last_mouse: Vector2;\n    element: HTMLElement | null;\n    visible_area: Vector4;\n    bindEvents(element: HTMLElement): void;\n    computeVisibleArea(): void;\n    onMouse(e: MouseEvent): void;\n    toCanvasContext(ctx: CanvasRenderingContext2D): void;\n    convertOffsetToCanvas(pos: Vector2): Vector2;\n    convertCanvasToOffset(pos: Vector2): Vector2;\n    mouseDrag(x: number, y: number): void;\n    changeScale(value: number, zooming_center?: Vector2): void;\n    changeDeltaScale(value: number, zooming_center?: Vector2): void;\n    reset(): void;\n}\n\n/**\n * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required.\n * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked\n *\n * @param canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself)\n * @param graph\n * @param options { skip_rendering, autoresize }\n */\nexport declare class LGraphCanvas {\n    static node_colors: Record<\n        string,\n        {\n            color: string;\n            bgcolor: string;\n            groupcolor: string;\n        }\n    >;\n    static link_type_colors: Record<string, string>;\n    static gradients: object;\n    static search_limit: number;\n\n    static getFileExtension(url: string): string;\n    static decodeHTML(str: string): string;\n\n    static onMenuCollapseAll(): void;\n    static onMenuNodeEdit(): void;\n    static onShowPropertyEditor(\n        item: any,\n        options: any,\n        e: any,\n        menu: any,\n        node: any\n    ): void;\n    /** Create menu for `Add Group` */\n    static onGroupAdd: ContextMenuEventListener;\n    /** Create menu for `Add Node` */\n    static onMenuAdd: ContextMenuEventListener;\n    static showMenuNodeOptionalInputs: ContextMenuEventListener;\n    static showMenuNodeOptionalOutputs: ContextMenuEventListener;\n    static onShowMenuNodeProperties: ContextMenuEventListener;\n    static onResizeNode: ContextMenuEventListener;\n    static onMenuNodeCollapse: ContextMenuEventListener;\n    static onMenuNodePin: ContextMenuEventListener;\n    static onMenuNodeMode: ContextMenuEventListener;\n    static onMenuNodeColors: ContextMenuEventListener;\n    static onMenuNodeShapes: ContextMenuEventListener;\n    static onMenuNodeRemove: ContextMenuEventListener;\n    static onMenuNodeClone: ContextMenuEventListener;\n\n    constructor(\n        canvas: HTMLCanvasElement | string,\n        graph?: LGraph,\n        options?: {\n            skip_render?: boolean;\n            autoresize?: boolean;\n        }\n    );\n\n    static active_canvas: HTMLCanvasElement;\n                           \n    allow_dragcanvas: boolean;\n    allow_dragnodes: boolean;\n    /** allow to control widgets, buttons, collapse, etc */\n    allow_interaction: boolean;\n    /** allows to change a connection with having to redo it again */\n    allow_reconnect_links: boolean;\n    /** allow selecting multi nodes without pressing extra keys */\n    multi_select: boolean;\n    /** No effect */\n    allow_searchbox: boolean;\n    always_render_background: boolean;\n    autoresize?: boolean;\n    background_image: string;\n    bgcanvas: HTMLCanvasElement;\n    bgctx: CanvasRenderingContext2D;\n    canvas: HTMLCanvasElement;\n    canvas_mouse: Vector2;\n    clear_background: boolean;\n    connecting_node: LGraphNode | null;\n    connections_width: number;\n    ctx: CanvasRenderingContext2D;\n    current_node: LGraphNode | null;\n    default_connection_color: {\n        input_off: string;\n        input_on: string;\n        output_off: string;\n        output_on: string;\n    };\n    default_link_color: string;\n    dirty_area: Vector4 | null;\n    dirty_bgcanvas?: boolean;\n    dirty_canvas?: boolean;\n    drag_mode: boolean;\n    dragging_canvas: boolean;\n    dragging_rectangle: Vector4 | null;\n    ds: DragAndScale;\n    /** used for transition */\n    editor_alpha: number;\n    filter: any;\n    fps: number;\n    frame: number;\n    graph: LGraph;\n    highlighted_links: Record<number, boolean>;\n    highquality_render: boolean;\n    inner_text_font: string;\n    is_rendering: boolean;\n    last_draw_time: number;\n    last_mouse: Vector2;\n    /**\n     * Possible duplicated with `last_mouse`\n     * https://github.com/jagenjo/litegraph.js/issues/70\n     */\n    last_mouse_position: Vector2;\n    /** Timestamp of last mouse click, defaults to 0 */\n    last_mouseclick: number;\n    links_render_mode:\n        | typeof LiteGraph.STRAIGHT_LINK\n        | typeof LiteGraph.LINEAR_LINK\n        | typeof LiteGraph.SPLINE_LINK;\n    live_mode: boolean;\n    node_capturing_input: LGraphNode | null;\n    node_dragged: LGraphNode | null;\n    node_in_panel: LGraphNode | null;\n    node_over: LGraphNode | null;\n    node_title_color: string;\n    node_widget: [LGraphNode, IWidget] | null;\n    /** Called by `LGraphCanvas.drawBackCanvas` */\n    onDrawBackground:\n        | ((ctx: CanvasRenderingContext2D, visibleArea: Vector4) => void)\n        | null;\n    /** Called by `LGraphCanvas.drawFrontCanvas` */\n    onDrawForeground:\n        | ((ctx: CanvasRenderingContext2D, visibleArea: Vector4) => void)\n        | null;\n    onDrawOverlay: ((ctx: CanvasRenderingContext2D) => void) | null;\n    /** Called by `LGraphCanvas.processMouseDown` */\n    onMouse: ((event: MouseEvent) => boolean) | null;\n    /** Called by `LGraphCanvas.drawFrontCanvas` and `LGraphCanvas.drawLinkTooltip` */\n    onDrawLinkTooltip: ((ctx: CanvasRenderingContext2D, link: LLink, _this: this) => void) | null;\n    /** Called by `LGraphCanvas.selectNodes` */\n    onNodeMoved: ((node: LGraphNode) => void) | null;\n    /** Called by `LGraphCanvas.processNodeSelected` */\n    onNodeSelected: ((node: LGraphNode) => void) | null;\n    /** Called by `LGraphCanvas.deselectNode` */\n    onNodeDeselected: ((node: LGraphNode) => void) | null;\n    /** Called by `LGraphCanvas.processNodeDblClicked` */\n    onShowNodePanel: ((node: LGraphNode) => void) | null;\n    /** Called by `LGraphCanvas.processNodeDblClicked` */\n    onNodeDblClicked: ((node: LGraphNode) => void) | null;\n    /** Called by `LGraphCanvas.selectNodes` */\n    onSelectionChange: ((nodes: Record<number, LGraphNode>) => void) | null;\n    /** Called by `LGraphCanvas.showSearchBox` */\n    onSearchBox:\n        | ((\n              helper: Element,\n              value: string,\n              graphCanvas: LGraphCanvas\n          ) => string[])\n        | null;\n    onSearchBoxSelection:\n        | ((name: string, event: MouseEvent, graphCanvas: LGraphCanvas) => void)\n        | null;\n    pause_rendering: boolean;\n    render_canvas_border: boolean;\n    render_collapsed_slots: boolean;\n    render_connection_arrows: boolean;\n    render_connections_border: boolean;\n    render_connections_shadows: boolean;\n    render_curved_connections: boolean;\n    render_execution_order: boolean;\n    render_only_selected: boolean;\n    render_shadows: boolean;\n    render_title_colored: boolean;\n    round_radius: number;\n    selected_group: null | LGraphGroup;\n    selected_group_resizing: boolean;\n    selected_nodes: Record<number, LGraphNode>;\n    show_info: boolean;\n    title_text_font: string;\n    /** set to true to render title bar with gradients */\n    use_gradients: boolean;\n    visible_area: DragAndScale[\"visible_area\"];\n    visible_links: LLink[];\n    visible_nodes: LGraphNode[];\n    zoom_modify_alpha: boolean;\n\n    /** clears all the data inside */\n    clear(): void;\n    /** assigns a graph, you can reassign graphs to the same canvas */\n    setGraph(graph: LGraph, skipClear?: boolean): void;\n    /** opens a graph contained inside a node in the current graph */\n    openSubgraph(graph: LGraph): void;\n    /** closes a subgraph contained inside a node */\n    closeSubgraph(): void;\n    /** assigns a canvas */\n    setCanvas(canvas: HTMLCanvasElement, skipEvents?: boolean): void;\n    /** binds mouse, keyboard, touch and drag events to the canvas */\n    bindEvents(): void;\n    /** unbinds mouse events from the canvas */\n    unbindEvents(): void;\n\n    /**\n     * this function allows to render the canvas using WebGL instead of Canvas2D\n     * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL\n     **/\n    enableWebGL(): void;\n\n    /**\n     * marks as dirty the canvas, this way it will be rendered again\n     * @param fg if the foreground canvas is dirty (the one containing the nodes)\n     * @param bg if the background canvas is dirty (the one containing the wires)\n     */\n    setDirty(fg: boolean, bg: boolean): void;\n\n    /**\n     * Used to attach the canvas in a popup\n     * @return the window where the canvas is attached (the DOM root node)\n     */\n    getCanvasWindow(): Window;\n    /** starts rendering the content of the canvas when needed */\n    startRendering(): void;\n    /** stops rendering the content of the canvas (to save resources) */\n    stopRendering(): void;\n\n    processMouseDown(e: MouseEvent): boolean | undefined;\n    processMouseMove(e: MouseEvent): boolean | undefined;\n    processMouseUp(e: MouseEvent): boolean | undefined;\n    processMouseWheel(e: MouseEvent): boolean | undefined;\n\n    /** returns true if a position (in graph space) is on top of a node little corner box */\n    isOverNodeBox(node: LGraphNode, canvasX: number, canvasY: number): boolean;\n    /** returns true if a position (in graph space) is on top of a node input slot */\n    isOverNodeInput(\n        node: LGraphNode,\n        canvasX: number,\n        canvasY: number,\n        slotPos: Vector2\n    ): boolean;\n\n    /** process a key event */\n    processKey(e: KeyboardEvent): boolean | undefined;\n\n    copyToClipboard(): void;\n    pasteFromClipboard(): void;\n    processDrop(e: DragEvent): void;\n    checkDropItem(e: DragEvent): void;\n    processNodeDblClicked(n: LGraphNode): void;\n    processNodeSelected(n: LGraphNode, e: MouseEvent): void;\n    processNodeDeselected(node: LGraphNode): void;\n\n    /** selects a given node (or adds it to the current selection) */\n    selectNode(node: LGraphNode, add?: boolean): void;\n    /** selects several nodes (or adds them to the current selection) */\n    selectNodes(nodes?: LGraphNode[], add?: boolean): void;\n    /** removes a node from the current selection */\n    deselectNode(node: LGraphNode): void;\n    /** removes all nodes from the current selection */\n    deselectAllNodes(): void;\n    /** deletes all nodes in the current selection from the graph */\n    deleteSelectedNodes(): void;\n\n    /** centers the camera on a given node */\n    centerOnNode(node: LGraphNode): void;\n    /** changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom */\n    setZoom(value: number, center: Vector2): void;\n    /** brings a node to front (above all other nodes) */\n    bringToFront(node: LGraphNode): void;\n    /** sends a node to the back (below all other nodes) */\n    sendToBack(node: LGraphNode): void;\n    /** checks which nodes are visible (inside the camera area) */\n    computeVisibleNodes(nodes: LGraphNode[]): LGraphNode[];\n    /** renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) */\n    draw(forceFG?: boolean, forceBG?: boolean): void;\n    /** draws the front canvas (the one containing all the nodes) */\n    drawFrontCanvas(): void;\n    /** draws some useful stats in the corner of the canvas */\n    renderInfo(ctx: CanvasRenderingContext2D, x: number, y: number): void;\n    /** draws the back canvas (the one containing the background and the connections) */\n    drawBackCanvas(): void;\n    /** draws the given node inside the canvas */\n    drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void;\n    /** draws graphic for node's slot */\n    drawSlotGraphic(ctx: CanvasRenderingContext2D, pos: number[], shape: SlotShape, horizontal: boolean): void;\n    /** draws the shape of the given node in the canvas */\n    drawNodeShape(\n        node: LGraphNode,\n        ctx: CanvasRenderingContext2D,\n        size: [number, number],\n        fgColor: string,\n        bgColor: string,\n        selected: boolean,\n        mouseOver: boolean\n    ): void;\n    /** draws every connection visible in the canvas */\n    drawConnections(ctx: CanvasRenderingContext2D): void;\n    /**\n     * draws a link between two points\n     * @param a start pos\n     * @param b end pos\n     * @param link the link object with all the link info\n     * @param skipBorder ignore the shadow of the link\n     * @param flow show flow animation (for events)\n     * @param color the color for the link\n     * @param startDir the direction enum\n     * @param endDir the direction enum\n     * @param numSublines number of sublines (useful to represent vec3 or rgb)\n     **/\n    renderLink(\n        a: Vector2,\n        b: Vector2,\n        link: object,\n        skipBorder: boolean,\n        flow: boolean,\n        color?: string,\n        startDir?: number,\n        endDir?: number,\n        numSublines?: number\n    ): void;\n\n    computeConnectionPoint(\n        a: Vector2,\n        b: Vector2,\n        t: number,\n        startDir?: number,\n        endDir?: number\n    ): void;\n\n    drawExecutionOrder(ctx: CanvasRenderingContext2D): void;\n    /** draws the widgets stored inside a node */\n    drawNodeWidgets(\n        node: LGraphNode,\n        posY: number,\n        ctx: CanvasRenderingContext2D,\n        activeWidget: object\n    ): void;\n    /** process an event on widgets */\n    processNodeWidgets(\n        node: LGraphNode,\n        pos: Vector2,\n        event: Event,\n        activeWidget: object\n    ): void;\n    /** draws every group area in the background */\n    drawGroups(canvas: any, ctx: CanvasRenderingContext2D): void;\n    adjustNodesSize(): void;\n    /** resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode */\n    resize(width?: number, height?: number): void;\n    /**\n     * switches to live mode (node shapes are not rendered, only the content)\n     * this feature was designed when graphs where meant to create user interfaces\n     **/\n    switchLiveMode(transition?: boolean): void;\n    onNodeSelectionChange(): void;\n    touchHandler(event: TouchEvent): void;\n\n    showLinkMenu(link: LLink, e: any): false;\n    prompt(\n        title: string,\n        value: any,\n        callback: Function,\n        event: any\n    ): HTMLDivElement;\n    showSearchBox(event?: MouseEvent): void;\n    showEditPropertyValue(node: LGraphNode, property: any, options: any): void;\n    createDialog(\n        html: string,\n        options?: { position?: Vector2; event?: MouseEvent }\n    ): void;\n\n    convertOffsetToCanvas: DragAndScale[\"convertOffsetToCanvas\"];\n    convertCanvasToOffset: DragAndScale[\"convertCanvasToOffset\"];\n    /** converts event coordinates from canvas2D to graph coordinates */\n    convertEventToCanvasOffset(e: MouseEvent): Vector2;\n    /** adds some useful properties to a mouse event, like the position in graph coordinates */\n    adjustMouseEvent(e: MouseEvent): void;\n\n    getCanvasMenuOptions(): ContextMenuItem[];\n    getNodeMenuOptions(node: LGraphNode): ContextMenuItem[];\n    getGroupMenuOptions(): ContextMenuItem[];\n    /** Called by `getCanvasMenuOptions`, replace default options */\n    getMenuOptions?(): ContextMenuItem[];\n    /** Called by `getCanvasMenuOptions`, append to default options */\n    getExtraMenuOptions?(): ContextMenuItem[];\n    /** Called when mouse right click */\n    processContextMenu(node: LGraphNode, event: Event): void;\n}\n\ndeclare class ContextMenu {\n    static trigger(\n        element: HTMLElement,\n        event_name: string,\n        params: any,\n        origin: any\n    ): void;\n    static isCursorOverElement(event: MouseEvent, element: HTMLElement): void;\n    static closeAllContextMenus(window: Window): void;\n    constructor(values: ContextMenuItem[], options?: IContextMenuOptions, window?: Window);\n    options: IContextMenuOptions;\n    parentMenu?: ContextMenu;\n    lock: boolean;\n    current_submenu?: ContextMenu;\n    addItem(\n        name: string,\n        value: ContextMenuItem,\n        options?: IContextMenuOptions\n    ): void;\n    close(e?: MouseEvent, ignore_parent_menu?: boolean): void;\n    getTopMenu(): void;\n    getFirstEvent(): void;\n}\n\ndeclare function clamp(v: number, min: number, max: number): number;\n"
  },
  {
    "path": "src/litegraph.js",
    "content": "\n(function(global) {\n    // *************************************************************\n    //   LiteGraph CLASS                                     *******\n    // *************************************************************\n\n    /**\n     * The Global Scope. It contains all the registered node classes.\n     *\n     * @class LiteGraph\n     * @constructor\n     */\n\n    var LiteGraph = (global.LiteGraph = {\n        VERSION: 0.4,\n\n        CANVAS_GRID_SIZE: 10,\n\n        NODE_TITLE_HEIGHT: 30,\n        NODE_TITLE_TEXT_Y: 20,\n        NODE_SLOT_HEIGHT: 20,\n        NODE_WIDGET_HEIGHT: 20,\n        NODE_WIDTH: 140,\n        NODE_MIN_WIDTH: 50,\n        NODE_COLLAPSED_RADIUS: 10,\n        NODE_COLLAPSED_WIDTH: 80,\n        NODE_TITLE_COLOR: \"#999\",\n        NODE_SELECTED_TITLE_COLOR: \"#FFF\",\n        NODE_TEXT_SIZE: 14,\n        NODE_TEXT_COLOR: \"#AAA\",\n        NODE_SUBTEXT_SIZE: 12,\n        NODE_DEFAULT_COLOR: \"#333\",\n        NODE_DEFAULT_BGCOLOR: \"#353535\",\n        NODE_DEFAULT_BOXCOLOR: \"#666\",\n        NODE_DEFAULT_SHAPE: \"box\",\n        NODE_BOX_OUTLINE_COLOR: \"#FFF\",\n        DEFAULT_SHADOW_COLOR: \"rgba(0,0,0,0.5)\",\n        DEFAULT_GROUP_FONT: 24,\n\n        WIDGET_BGCOLOR: \"#222\",\n        WIDGET_OUTLINE_COLOR: \"#666\",\n        WIDGET_TEXT_COLOR: \"#DDD\",\n        WIDGET_SECONDARY_TEXT_COLOR: \"#999\",\n\n        LINK_COLOR: \"#9A9\",\n        EVENT_LINK_COLOR: \"#A86\",\n        CONNECTING_LINK_COLOR: \"#AFA\",\n\n        MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops\n        DEFAULT_POSITION: [100, 100], //default node position\n        VALID_SHAPES: [\"default\", \"box\", \"round\", \"card\"], //,\"circle\"\n\n        //shapes are used for nodes but also for slots\n        BOX_SHAPE: 1,\n        ROUND_SHAPE: 2,\n        CIRCLE_SHAPE: 3,\n        CARD_SHAPE: 4,\n        ARROW_SHAPE: 5,\n        GRID_SHAPE: 6, // intended for slot arrays\n\n        //enums\n        INPUT: 1,\n        OUTPUT: 2,\n\n        EVENT: -1, //for outputs\n        ACTION: -1, //for inputs\n\n        NODE_MODES: [\"Always\", \"On Event\", \"Never\", \"On Trigger\"], // helper, will add \"On Request\" and more in the future\n        NODE_MODES_COLORS:[\"#666\",\"#422\",\"#333\",\"#224\",\"#626\"], // use with node_box_coloured_by_mode\n        ALWAYS: 0,\n        ON_EVENT: 1,\n        NEVER: 2,\n        ON_TRIGGER: 3,\n\n        UP: 1,\n        DOWN: 2,\n        LEFT: 3,\n        RIGHT: 4,\n        CENTER: 5,\n\n        LINK_RENDER_MODES: [\"Straight\", \"Linear\", \"Spline\"], // helper\n        STRAIGHT_LINK: 0,\n        LINEAR_LINK: 1,\n        SPLINE_LINK: 2,\n\n        NORMAL_TITLE: 0,\n        NO_TITLE: 1,\n        TRANSPARENT_TITLE: 2,\n        AUTOHIDE_TITLE: 3,\n        VERTICAL_LAYOUT: \"vertical\", // arrange nodes vertically\n\n        proxy: null, //used to redirect calls\n        node_images_path: \"\",\n\n        debug: false,\n        catch_exceptions: true,\n        throw_errors: true,\n        allow_scripts: false, //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits\n        use_deferred_actions: true, //executes actions during the graph execution flow\n        registered_node_types: {}, //nodetypes by string\n        node_types_by_file_extension: {}, //used for dropping files in the canvas\n        Nodes: {}, //node types by classname\n\t\tGlobals: {}, //used to store vars between graphs\n\n        searchbox_extras: {}, //used to add extra features to the search box\n        auto_sort_node_types: false, // [true!] If set to true, will automatically sort node types / categories in the context menus\n\t\t\n\t\tnode_box_coloured_when_on: false, // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback\n        node_box_coloured_by_mode: false, // [true!] nodebox based on node mode, visual feedback\n        \n        dialog_close_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false\n        dialog_close_on_mouse_leave_delay: 500,\n        \n        shift_click_do_break_link_from: false, // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys\n        click_do_break_link_to: false, // [false!]prefer false, way too easy to break links\n        \n        search_hide_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false\n        search_filter_enabled: false, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out]\n        search_show_all_on_open: true, // [true!] opens the results list when opening the search widget\n        \n        auto_load_slot_types: false, // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out]\n        \n\t\t// set these values if not using auto_load_slot_types\n        registered_slot_in_types: {}, // slot types for nodeclass\n        registered_slot_out_types: {}, // slot types for nodeclass\n        slot_types_in: [], // slot types IN\n        slot_types_out: [], // slot types OUT\n        slot_types_default_in: [], // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search\n\t\tslot_types_default_out: [], // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search\n\t\t\n\t\talt_drag_do_clone_nodes: false, // [true!] very handy, ALT click to clone and drag the new node\n\n\t\tdo_add_triggers_slots: false, // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this\n\t\t\n\t\tallow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentially, one by one\n\n\t\tmiddle_click_slot_add_default_node: false, //[true!] allows to create and connect a ndoe clicking with the third button (wheel)\n\t\t\n\t\trelease_link_on_empty_shows_menu: false, //[true!] dragging a link to empty space will open a menu, add from list, search or defaults\n\t\t\n        pointerevents_method: \"mouse\", // \"mouse\"|\"pointer\" use mouse for retrocompatibility issues? (none found @ now)\n        // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary)\n\n        ctrl_shift_v_paste_connect_unselected_outputs: false, //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes\n\n        // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers.\n        // use this if you must have node IDs that are unique across all graphs and subgraphs.\n        use_uuids: false,\n\n        /**\n         * Register a node class so it can be listed when the user wants to create a new one\n         * @method registerNodeType\n         * @param {String} type name of the node and path\n         * @param {Class} base_class class containing the structure of a node\n         */\n\n        registerNodeType: function(type, base_class) {\n            if (!base_class.prototype) {\n                throw \"Cannot register a simple object, it must be a class with a prototype\";\n            }\n            base_class.type = type;\n\n            if (LiteGraph.debug) {\n                console.log(\"Node registered: \" + type);\n            }\n\n            const classname = base_class.name;\n\n            const pos = type.lastIndexOf(\"/\");\n            base_class.category = type.substring(0, pos);\n\n            if (!base_class.title) {\n                base_class.title = classname;\n            }\n\n            //extend class\n            for (var i in LGraphNode.prototype) {\n                if (!base_class.prototype[i]) {\n                    base_class.prototype[i] = LGraphNode.prototype[i];\n                }\n            }\n\n            const prev = this.registered_node_types[type];\n            if(prev) {\n                console.log(\"replacing node type: \" + type);\n            }\n            if( !Object.prototype.hasOwnProperty.call( base_class.prototype, \"shape\") ) {\n                Object.defineProperty(base_class.prototype, \"shape\", {\n                    set: function(v) {\n                        switch (v) {\n                            case \"default\":\n                                delete this._shape;\n                                break;\n                            case \"box\":\n                                this._shape = LiteGraph.BOX_SHAPE;\n                                break;\n                            case \"round\":\n                                this._shape = LiteGraph.ROUND_SHAPE;\n                                break;\n                            case \"circle\":\n                                this._shape = LiteGraph.CIRCLE_SHAPE;\n                                break;\n                            case \"card\":\n                                this._shape = LiteGraph.CARD_SHAPE;\n                                break;\n                            default:\n                                this._shape = v;\n                        }\n                    },\n                    get: function() {\n                        return this._shape;\n                    },\n                    enumerable: true,\n                    configurable: true\n                });\n                \n\n                //used to know which nodes to create when dragging files to the canvas\n                if (base_class.supported_extensions) {\n                    for (let i in base_class.supported_extensions) {\n                        const ext = base_class.supported_extensions[i];\n                        if(ext && ext.constructor === String) {\n                            this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class;\n                        }\n                    }\n                }\n            }\n\n            this.registered_node_types[type] = base_class;\n            if (base_class.constructor.name) {\n                this.Nodes[classname] = base_class;\n            }\n            if (LiteGraph.onNodeTypeRegistered) {\n                LiteGraph.onNodeTypeRegistered(type, base_class);\n            }\n            if (prev && LiteGraph.onNodeTypeReplaced) {\n                LiteGraph.onNodeTypeReplaced(type, base_class, prev);\n            }\n\n            //warnings\n            if (base_class.prototype.onPropertyChange) {\n                console.warn(\n                    \"LiteGraph node class \" +\n                        type +\n                        \" has onPropertyChange method, it must be called onPropertyChanged with d at the end\"\n                );\n            }\n            \n            // TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types\n            if (this.auto_load_slot_types) {\n                new base_class(base_class.title || \"tmpnode\");\n            }\n        },\n\n        /**\n         * removes a node type from the system\n         * @method unregisterNodeType\n         * @param {String|Object} type name of the node or the node constructor itself\n         */\n        unregisterNodeType: function(type) {\n            const base_class =\n                type.constructor === String\n                    ? this.registered_node_types[type]\n                    : type;\n            if (!base_class) {\n                throw \"node type not found: \" + type;\n            }\n            delete this.registered_node_types[base_class.type];\n            if (base_class.constructor.name) {\n                delete this.Nodes[base_class.constructor.name];\n            }\n        },\n\n        /**\n        * Save a slot type and his node\n        * @method registerSlotType\n        * @param {String|Object} type name of the node or the node constructor itself\n        * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, ..\n        */\n        registerNodeAndSlotType: function(type, slot_type, out){\n            out = out || false;\n            const base_class =\n                type.constructor === String &&\n                this.registered_node_types[type] !== \"anonymous\"\n                    ? this.registered_node_types[type]\n                    : type;\n\n            const class_type = base_class.constructor.type;\n\n            let allTypes = [];\n            if (typeof slot_type === \"string\") {\n                allTypes = slot_type.split(\",\");\n            } else if (slot_type == this.EVENT || slot_type == this.ACTION) {\n                allTypes = [\"_event_\"];\n            } else {\n                allTypes = [\"*\"];\n            }\n\n            for (let i = 0; i < allTypes.length; ++i) {\n                let slotType = allTypes[i];\n                if (slotType === \"\") {\n                    slotType = \"*\";\n                }\n                const registerTo = out\n                    ? \"registered_slot_out_types\"\n                    : \"registered_slot_in_types\";\n                if (this[registerTo][slotType] === undefined) {\n                    this[registerTo][slotType] = { nodes: [] };\n                }\n                if (!this[registerTo][slotType].nodes.includes(class_type)) {\n                    this[registerTo][slotType].nodes.push(class_type);\n                }\n\n                // check if is a new type\n                if (!out) {\n                    if (!this.slot_types_in.includes(slotType.toLowerCase())) {\n                        this.slot_types_in.push(slotType.toLowerCase());\n                        this.slot_types_in.sort();\n                    }\n                } else {\n                    if (!this.slot_types_out.includes(slotType.toLowerCase())) {\n                        this.slot_types_out.push(slotType.toLowerCase());\n                        this.slot_types_out.sort();\n                    }\n                }\n            }\n        },\n        \n        /**\n         * Create a new nodetype by passing an object with some properties\n         * like onCreate, inputs:Array, outputs:Array, properties, onExecute\n         * @method buildNodeClassFromObject\n         * @param {String} name node name with namespace (p.e.: 'math/sum')\n         * @param {Object} object methods expected onCreate, inputs, outputs, properties, onExecute\n         */\n         buildNodeClassFromObject: function(\n            name,\n            object\n        ) {\n            var ctor_code = \"\";\n            if(object.inputs)\n            for(var i=0; i < object.inputs.length; ++i)\n            {\n                var _name = object.inputs[i][0];\n                var _type = object.inputs[i][1];\n                if(_type && _type.constructor === String)\n                    _type = '\"'+_type+'\"';\n                ctor_code += \"this.addInput('\"+_name+\"',\"+_type+\");\\n\";\n            }\n            if(object.outputs)\n            for(var i=0; i < object.outputs.length; ++i)\n            {\n                var _name = object.outputs[i][0];\n                var _type = object.outputs[i][1];\n                if(_type && _type.constructor === String)\n                    _type = '\"'+_type+'\"';\n                ctor_code += \"this.addOutput('\"+_name+\"',\"+_type+\");\\n\";\n            }\n            if(object.properties)\n            for(var i in object.properties)\n            {\n                var prop = object.properties[i];\n                if(prop && prop.constructor === String)\n                    prop = '\"'+prop+'\"';\n                ctor_code += \"this.addProperty('\"+i+\"',\"+prop+\");\\n\";\n            }\n            ctor_code += \"if(this.onCreate)this.onCreate()\";\n            var classobj = Function(ctor_code);\n            for(var i in object)\n                if(i!=\"inputs\" && i!=\"outputs\" && i!=\"properties\")\n                    classobj.prototype[i] = object[i];\n            classobj.title = object.title || name.split(\"/\").pop();\n            classobj.desc = object.desc || \"Generated from object\";\n            this.registerNodeType(name, classobj);\n            return classobj;\n        },\n        \n        /**\n         * 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.\n         * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output.\n         * @method wrapFunctionAsNode\n         * @param {String} name node name with namespace (p.e.: 'math/sum')\n         * @param {Function} func\n         * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type\n         * @param {String} return_type [optional] string with the return type, otherwise it will be generic\n         * @param {Object} properties [optional] properties to be configurable\n         */\n        wrapFunctionAsNode: function(\n            name,\n            func,\n            param_types,\n            return_type,\n            properties\n        ) {\n            var params = Array(func.length);\n            var code = \"\";\n            if(param_types !== null) //null means no inputs\n            {\n                var names = LiteGraph.getParameterNames(func);\n                for (var i = 0; i < names.length; ++i) {\n                    var type = 0;\n                    if(param_types)\n                    {\n                        //type = param_types[i] != null ? \"'\" + param_types[i] + \"'\" : \"0\";\n                        if( param_types[i] != null && param_types[i].constructor === String )\n                            type = \"'\" + param_types[i] + \"'\" ;\n                        else if( param_types[i] != null )\n                            type = param_types[i];\n                    } \n                    code +=\n                        \"this.addInput('\" +\n                        names[i] +\n                        \"',\" +\n                        type +\n                        \");\\n\";\n                }\n            }\n            if(return_type !== null) //null means no output\n            code +=\n                \"this.addOutput('out',\" +\n                (return_type != null ? (return_type.constructor === String ? \"'\" + return_type + \"'\" : return_type) : 0) +\n                \");\\n\";\n            if (properties) {\n                code +=\n                    \"this.properties = \" + JSON.stringify(properties) + \";\\n\";\n            }\n            var classobj = Function(code);\n            classobj.title = name.split(\"/\").pop();\n            classobj.desc = \"Generated from \" + func.name;\n            classobj.prototype.onExecute = function onExecute() {\n                for (var i = 0; i < params.length; ++i) {\n                    params[i] = this.getInputData(i);\n                }\n                var r = func.apply(this, params);\n                this.setOutputData(0, r);\n            };\n            this.registerNodeType(name, classobj);\n            return classobj;\n        },\n\n        /**\n         * Removes all previously registered node's types\n         */\n        clearRegisteredTypes: function() {\n            this.registered_node_types = {};\n            this.node_types_by_file_extension = {};\n            this.Nodes = {};\n            this.searchbox_extras = {};\n        },\n\n        /**\n         * Adds this method to all nodetypes, existing and to be created\n         * (You can add it to LGraphNode.prototype but then existing node types wont have it)\n         * @method addNodeMethod\n         * @param {Function} func\n         */\n        addNodeMethod: function(name, func) {\n            LGraphNode.prototype[name] = func;\n            for (var i in this.registered_node_types) {\n                var type = this.registered_node_types[i];\n                if (type.prototype[name]) {\n                    type.prototype[\"_\" + name] = type.prototype[name];\n                } //keep old in case of replacing\n                type.prototype[name] = func;\n            }\n        },\n\n        /**\n         * Create a node of a given type with a name. The node is not attached to any graph yet.\n         * @method createNode\n         * @param {String} type full name of the node class. p.e. \"math/sin\"\n         * @param {String} name a name to distinguish from other nodes\n         * @param {Object} options to set options\n         */\n\n        createNode: function(type, title, options) {\n            var base_class = this.registered_node_types[type];\n            if (!base_class) {\n                if (LiteGraph.debug) {\n                    console.log(\n                        'GraphNode type \"' + type + '\" not registered.'\n                    );\n                }\n                return null;\n            }\n\n            var prototype = base_class.prototype || base_class;\n\n            title = title || base_class.title || type;\n\n            var node = null;\n\n            if (LiteGraph.catch_exceptions) {\n                try {\n                    node = new base_class(title);\n                } catch (err) {\n                    console.error(err);\n                    return null;\n                }\n            } else {\n                node = new base_class(title);\n            }\n\n            node.type = type;\n\n            if (!node.title && title) {\n                node.title = title;\n            }\n            if (!node.properties) {\n                node.properties = {};\n            }\n            if (!node.properties_info) {\n                node.properties_info = [];\n            }\n            if (!node.flags) {\n                node.flags = {};\n            }\n            if (!node.size) {\n                node.size = node.computeSize();\n\t\t\t\t//call onresize?\n            }\n            if (!node.pos) {\n                node.pos = LiteGraph.DEFAULT_POSITION.concat();\n            }\n            if (!node.mode) {\n                node.mode = LiteGraph.ALWAYS;\n            }\n\n            //extra options\n            if (options) {\n                for (var i in options) {\n                    node[i] = options[i];\n                }\n            }\n\n\t\t\t// callback\n            if ( node.onNodeCreated ) {\n                node.onNodeCreated();\n            }\n            \n            return node;\n        },\n\n        /**\n         * Returns a registered node type with a given name\n         * @method getNodeType\n         * @param {String} type full name of the node class. p.e. \"math/sin\"\n         * @return {Class} the node class\n         */\n        getNodeType: function(type) {\n            return this.registered_node_types[type];\n        },\n\n        /**\n         * Returns a list of node types matching one category\n         * @method getNodeType\n         * @param {String} category category name\n         * @return {Array} array with all the node classes\n         */\n\n        getNodeTypesInCategory: function(category, filter) {\n            var r = [];\n            for (var i in this.registered_node_types) {\n                var type = this.registered_node_types[i];\n                if (type.filter != filter) {\n                    continue;\n                }\n\n                if (category == \"\") {\n                    if (type.category == null) {\n                        r.push(type);\n                    }\n                } else if (type.category == category) {\n                    r.push(type);\n                }\n            }\n\n            if (this.auto_sort_node_types) {\n                r.sort(function(a,b){return a.title.localeCompare(b.title)});\n            }\n\n            return r;\n        },\n\n        /**\n         * Returns a list with all the node type categories\n         * @method getNodeTypesCategories\n         * @param {String} filter only nodes with ctor.filter equal can be shown\n         * @return {Array} array with all the names of the categories\n         */\n        getNodeTypesCategories: function( filter ) {\n            var categories = { \"\": 1 };\n            for (var i in this.registered_node_types) {\n\t\t\t\tvar type = this.registered_node_types[i];\n                if ( type.category && !type.skip_list )\n                {\n\t\t\t\t\tif(type.filter != filter)\n\t\t\t\t\t\tcontinue;\n                    categories[type.category] = 1;\n                }\n            }\n            var result = [];\n            for (var i in categories) {\n                result.push(i);\n            }\n            return this.auto_sort_node_types ? result.sort() : result;\n        },\n\n        //debug purposes: reloads all the js scripts that matches a wildcard\n        reloadNodes: function(folder_wildcard) {\n            var tmp = document.getElementsByTagName(\"script\");\n            //weird, this array changes by its own, so we use a copy\n            var script_files = [];\n            for (var i=0; i < tmp.length; i++) {\n                script_files.push(tmp[i]);\n            }\n\n            var docHeadObj = document.getElementsByTagName(\"head\")[0];\n            folder_wildcard = document.location.href + folder_wildcard;\n\n            for (var i=0; i < script_files.length; i++) {\n                var src = script_files[i].src;\n                if (\n                    !src ||\n                    src.substr(0, folder_wildcard.length) != folder_wildcard\n                ) {\n                    continue;\n                }\n\n                try {\n                    if (LiteGraph.debug) {\n                        console.log(\"Reloading: \" + src);\n                    }\n                    var dynamicScript = document.createElement(\"script\");\n                    dynamicScript.type = \"text/javascript\";\n                    dynamicScript.src = src;\n                    docHeadObj.appendChild(dynamicScript);\n                    docHeadObj.removeChild(script_files[i]);\n                } catch (err) {\n                    if (LiteGraph.throw_errors) {\n                        throw err;\n                    }\n                    if (LiteGraph.debug) {\n                        console.log(\"Error while reloading \" + src);\n                    }\n                }\n            }\n\n            if (LiteGraph.debug) {\n                console.log(\"Nodes reloaded\");\n            }\n        },\n\n        //separated just to improve if it doesn't work\n        cloneObject: function(obj, target) {\n            if (obj == null) {\n                return null;\n            }\n            var r = JSON.parse(JSON.stringify(obj));\n            if (!target) {\n                return r;\n            }\n\n            for (var i in r) {\n                target[i] = r[i];\n            }\n            return target;\n        },\n\n        /*\n         * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670\n         */\n        uuidv4: function() {\n            return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,a=>(a^Math.random()*16>>a/4).toString(16));\n        },\n\n        /**\n         * Returns if the types of two slots are compatible (taking into account wildcards, etc)\n         * @method isValidConnection\n         * @param {String} type_a\n         * @param {String} type_b\n         * @return {Boolean} true if they can be connected\n         */\n        isValidConnection: function(type_a, type_b) {\n\t\t\tif (type_a==\"\" || type_a===\"*\") type_a = 0;\n\t\t\tif (type_b==\"\" || type_b===\"*\") type_b = 0;\n            if (\n                !type_a //generic output\n                || !type_b // generic input\n                || type_a == type_b //same type (is valid for triggers)\n                || (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION)\n            ) {\n                return true;\n            }\n\n            // Enforce string type to handle toLowerCase call (-1 number not ok)\n            type_a = String(type_a);\n            type_b = String(type_b);\n            type_a = type_a.toLowerCase();\n            type_b = type_b.toLowerCase();\n\n            // For nodes supporting multiple connection types\n            if (type_a.indexOf(\",\") == -1 && type_b.indexOf(\",\") == -1) {\n                return type_a == type_b;\n            }\n\n            // Check all permutations to see if one is valid\n            var supported_types_a = type_a.split(\",\");\n            var supported_types_b = type_b.split(\",\");\n            for (var i = 0; i < supported_types_a.length; ++i) {\n                for (var j = 0; j < supported_types_b.length; ++j) {\n                    if(this.isValidConnection(supported_types_a[i],supported_types_b[j])){\n\t\t\t\t\t//if (supported_types_a[i] == supported_types_b[j]) {\n                        return true;\n                    }\n                }\n            }\n\n            return false;\n        },\n\n        /**\n         * Register a string in the search box so when the user types it it will recommend this node\n         * @method registerSearchboxExtra\n         * @param {String} node_type the node recommended\n         * @param {String} description text to show next to it\n         * @param {Object} data it could contain info of how the node should be configured\n         * @return {Boolean} true if they can be connected\n         */\n        registerSearchboxExtra: function(node_type, description, data) {\n            this.searchbox_extras[description.toLowerCase()] = {\n                type: node_type,\n                desc: description,\n                data: data\n            };\n        },\n\n        /**\n         * Wrapper to load files (from url using fetch or from file using FileReader)\n         * @method fetchFile\n         * @param {String|File|Blob} url the url of the file (or the file itself)\n         * @param {String} type an string to know how to fetch it: \"text\",\"arraybuffer\",\"json\",\"blob\"\n         * @param {Function} on_complete callback(data)\n         * @param {Function} on_error in case of an error\n         * @return {FileReader|Promise} returns the object used to \n         */\n\t\tfetchFile: function( url, type, on_complete, on_error ) {\n\t\t\tvar that = this;\n\t\t\tif(!url)\n\t\t\t\treturn null;\n\n\t\t\ttype = type || \"text\";\n\t\t\tif( url.constructor === String )\n\t\t\t{\n\t\t\t\tif (url.substr(0, 4) == \"http\" && LiteGraph.proxy) {\n\t\t\t\t\turl = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\n\t\t\t\t}\n\t\t\t\treturn fetch(url)\n\t\t\t\t.then(function(response) {\n\t\t\t\t\tif(!response.ok)\n\t\t\t\t\t\t throw new Error(\"File not found\"); //it will be catch below\n\t\t\t\t\tif(type == \"arraybuffer\")\n\t\t\t\t\t\treturn response.arrayBuffer();\n\t\t\t\t\telse if(type == \"text\" || type == \"string\")\n\t\t\t\t\t\treturn response.text();\n\t\t\t\t\telse if(type == \"json\")\n\t\t\t\t\t\treturn response.json();\n\t\t\t\t\telse if(type == \"blob\")\n\t\t\t\t\t\treturn response.blob();\n\t\t\t\t})\n\t\t\t\t.then(function(data) {\n\t\t\t\t\tif(on_complete)\n\t\t\t\t\t\ton_complete(data);\n\t\t\t\t})\n\t\t\t\t.catch(function(error) {\n\t\t\t\t\tconsole.error(\"error fetching file:\",url);\n\t\t\t\t\tif(on_error)\n\t\t\t\t\t\ton_error(error);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if( url.constructor === File || url.constructor === Blob)\n\t\t\t{\n\t\t\t\tvar reader = new FileReader();\n\t\t\t\treader.onload = function(e)\n\t\t\t\t{\n\t\t\t\t\tvar v = e.target.result;\n\t\t\t\t\tif( type == \"json\" )\n\t\t\t\t\t\tv = JSON.parse(v);\n\t\t\t\t\tif(on_complete)\n\t\t\t\t\t\ton_complete(v);\n\t\t\t\t}\n\t\t\t\tif(type == \"arraybuffer\")\n\t\t\t\t\treturn reader.readAsArrayBuffer(url);\n\t\t\t\telse if(type == \"text\" || type == \"json\")\n\t\t\t\t\treturn reader.readAsText(url);\n\t\t\t\telse if(type == \"blob\")\n\t\t\t\t\treturn reader.readAsBinaryString(url);\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n    });\n\n    //timer that works everywhere\n    if (typeof performance != \"undefined\") {\n        LiteGraph.getTime = performance.now.bind(performance);\n    } else if (typeof Date != \"undefined\" && Date.now) {\n        LiteGraph.getTime = Date.now.bind(Date);\n    } else if (typeof process != \"undefined\") {\n        LiteGraph.getTime = function() {\n            var t = process.hrtime();\n            return t[0] * 0.001 + t[1] * 1e-6;\n        };\n    } else {\n        LiteGraph.getTime = function getTime() {\n            return new Date().getTime();\n        };\n    }\n\n    //*********************************************************************************\n    // LGraph CLASS\n    //*********************************************************************************\n\n    /**\n     * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.\n\t * supported callbacks:\n\t\t+ onNodeAdded: when a new node is added to the graph\n\t\t+ onNodeRemoved: when a node inside this graph is removed\n\t\t+ onNodeConnectionChange: some connection has changed in the graph (connected or disconnected)\n     *\n     * @class LGraph\n     * @constructor\n     * @param {Object} o data from previous serialization [optional]\n     */\n\n    function LGraph(o) {\n        if (LiteGraph.debug) {\n            console.log(\"Graph created\");\n        }\n        this.list_of_graphcanvas = null;\n        this.clear();\n\n        if (o) {\n            this.configure(o);\n        }\n    }\n\n    global.LGraph = LiteGraph.LGraph = LGraph;\n\n    //default supported types\n    LGraph.supported_types = [\"number\", \"string\", \"boolean\"];\n\n    //used to know which types of connections support this graph (some graphs do not allow certain types)\n    LGraph.prototype.getSupportedTypes = function() {\n        return this.supported_types || LGraph.supported_types;\n    };\n\n    LGraph.STATUS_STOPPED = 1;\n    LGraph.STATUS_RUNNING = 2;\n\n    /**\n     * Removes all nodes from this graph\n     * @method clear\n     */\n\n    LGraph.prototype.clear = function() {\n        this.stop();\n        this.status = LGraph.STATUS_STOPPED;\n\n        this.last_node_id = 0;\n        this.last_link_id = 0;\n\n        this._version = -1; //used to detect changes\n\n        //safe clear\n        if (this._nodes) {\n            for (var i = 0; i < this._nodes.length; ++i) {\n                var node = this._nodes[i];\n                if (node.onRemoved) {\n                    node.onRemoved();\n                }\n            }\n        }\n\n        //nodes\n        this._nodes = [];\n        this._nodes_by_id = {};\n        this._nodes_in_order = []; //nodes sorted in execution order\n        this._nodes_executable = null; //nodes that contain onExecute sorted in execution order\n\n        //other scene stuff\n        this._groups = [];\n\n        //links\n        this.links = {}; //container with all the links\n\n        //iterations\n        this.iteration = 0;\n\n        //custom data\n        this.config = {};\n\t\tthis.vars = {};\n\t\tthis.extra = {}; //to store custom data\n\n        //timing\n        this.globaltime = 0;\n        this.runningtime = 0;\n        this.fixedtime = 0;\n        this.fixedtime_lapse = 0.01;\n        this.elapsed_time = 0.01;\n        this.last_update_time = 0;\n        this.starttime = 0;\n\n        this.catch_errors = true;\n\n        this.nodes_executing = [];\n        this.nodes_actioning = [];\n        this.nodes_executedAction = [];\n        \n        //subgraph_data\n        this.inputs = {};\n        this.outputs = {};\n\n        //notify canvas to redraw\n        this.change();\n\n        this.sendActionToCanvas(\"clear\");\n    };\n\n    /**\n     * Attach Canvas to this graph\n     * @method attachCanvas\n     * @param {GraphCanvas} graph_canvas\n     */\n\n    LGraph.prototype.attachCanvas = function(graphcanvas) {\n        if (graphcanvas.constructor != LGraphCanvas) {\n            throw \"attachCanvas expects a LGraphCanvas instance\";\n        }\n        if (graphcanvas.graph && graphcanvas.graph != this) {\n            graphcanvas.graph.detachCanvas(graphcanvas);\n        }\n\n        graphcanvas.graph = this;\n\n        if (!this.list_of_graphcanvas) {\n            this.list_of_graphcanvas = [];\n        }\n        this.list_of_graphcanvas.push(graphcanvas);\n    };\n\n    /**\n     * Detach Canvas from this graph\n     * @method detachCanvas\n     * @param {GraphCanvas} graph_canvas\n     */\n    LGraph.prototype.detachCanvas = function(graphcanvas) {\n        if (!this.list_of_graphcanvas) {\n            return;\n        }\n\n        var pos = this.list_of_graphcanvas.indexOf(graphcanvas);\n        if (pos == -1) {\n            return;\n        }\n        graphcanvas.graph = null;\n        this.list_of_graphcanvas.splice(pos, 1);\n    };\n\n    /**\n     * Starts running this graph every interval milliseconds.\n     * @method start\n     * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate\n     */\n\n    LGraph.prototype.start = function(interval) {\n        if (this.status == LGraph.STATUS_RUNNING) {\n            return;\n        }\n        this.status = LGraph.STATUS_RUNNING;\n\n        if (this.onPlayEvent) {\n            this.onPlayEvent();\n        }\n\n        this.sendEventToAllNodes(\"onStart\");\n\n        //launch\n        this.starttime = LiteGraph.getTime();\n        this.last_update_time = this.starttime;\n        interval = interval || 0;\n        var that = this;\n\n\t\t//execute once per frame\n        if ( interval == 0 && typeof window != \"undefined\" && window.requestAnimationFrame ) {\n            function on_frame() {\n                if (that.execution_timer_id != -1) {\n                    return;\n                }\n                window.requestAnimationFrame(on_frame);\n\t\t\t\tif(that.onBeforeStep)\n\t\t\t\t\tthat.onBeforeStep();\n                that.runStep(1, !that.catch_errors);\n\t\t\t\tif(that.onAfterStep)\n\t\t\t\t\tthat.onAfterStep();\n            }\n            this.execution_timer_id = -1;\n            on_frame();\n        } else { //execute every 'interval' ms\n            this.execution_timer_id = setInterval(function() {\n                //execute\n\t\t\t\tif(that.onBeforeStep)\n\t\t\t\t\tthat.onBeforeStep();\n                that.runStep(1, !that.catch_errors);\n\t\t\t\tif(that.onAfterStep)\n\t\t\t\t\tthat.onAfterStep();\n            }, interval);\n        }\n    };\n\n    /**\n     * Stops the execution loop of the graph\n     * @method stop execution\n     */\n\n    LGraph.prototype.stop = function() {\n        if (this.status == LGraph.STATUS_STOPPED) {\n            return;\n        }\n\n        this.status = LGraph.STATUS_STOPPED;\n\n        if (this.onStopEvent) {\n            this.onStopEvent();\n        }\n\n        if (this.execution_timer_id != null) {\n            if (this.execution_timer_id != -1) {\n                clearInterval(this.execution_timer_id);\n            }\n            this.execution_timer_id = null;\n        }\n\n        this.sendEventToAllNodes(\"onStop\");\n    };\n\n    /**\n     * Run N steps (cycles) of the graph\n     * @method runStep\n     * @param {number} num number of steps to run, default is 1\n     * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors \n     * @param {number} limit max number of nodes to execute (used to execute from start to a node)\n     */\n\n    LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) {\n        num = num || 1;\n\n        var start = LiteGraph.getTime();\n        this.globaltime = 0.001 * (start - this.starttime);\n\n        //not optimal: executes possible pending actions in node, problem is it is not optimized\n        //it is done here as if it was done in the later loop it wont be called in the node missed the onExecute\n        \n        //from now on it will iterate only on executable nodes which is faster\n        var nodes = this._nodes_executable\n            ? this._nodes_executable\n            : this._nodes;\n        if (!nodes) {\n            return;\n        }\n\n\t\tlimit = limit || nodes.length;\n\n        if (do_not_catch_errors) {\n            //iterations\n            for (var i = 0; i < num; i++) {\n                for (var j = 0; j < limit; ++j) {\n                    var node = nodes[j];\n                    if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length)\n                        node.executePendingActions();\n                    if (node.mode == LiteGraph.ALWAYS && node.onExecute) {\n                        //wrap node.onExecute();\n\t\t\t\t\t\tnode.doExecute();\n                    }\n                }\n\n                this.fixedtime += this.fixedtime_lapse;\n                if (this.onExecuteStep) {\n                    this.onExecuteStep();\n                }\n            }\n\n            if (this.onAfterExecute) {\n                this.onAfterExecute();\n            }\n        } else { //catch errors\n            try {\n                //iterations\n                for (var i = 0; i < num; i++) {\n                    for (var j = 0; j < limit; ++j) {\n                        var node = nodes[j];\n                        if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length)\n                            node.executePendingActions();\n                        if (node.mode == LiteGraph.ALWAYS && node.onExecute) {\n                            node.onExecute();\n                        }\n                    }\n\n                    this.fixedtime += this.fixedtime_lapse;\n                    if (this.onExecuteStep) {\n                        this.onExecuteStep();\n                    }\n                }\n\n                if (this.onAfterExecute) {\n                    this.onAfterExecute();\n                }\n                this.errors_in_execution = false;\n            } catch (err) {\n                this.errors_in_execution = true;\n                if (LiteGraph.throw_errors) {\n                    throw err;\n                }\n                if (LiteGraph.debug) {\n                    console.log(\"Error during execution: \" + err);\n                }\n                this.stop();\n            }\n        }\n\n        var now = LiteGraph.getTime();\n        var elapsed = now - start;\n        if (elapsed == 0) {\n            elapsed = 1;\n        }\n        this.execution_time = 0.001 * elapsed;\n        this.globaltime += 0.001 * elapsed;\n        this.iteration += 1;\n        this.elapsed_time = (now - this.last_update_time) * 0.001;\n        this.last_update_time = now;\n        this.nodes_executing = [];\n        this.nodes_actioning = [];\n        this.nodes_executedAction = [];\n    };\n\n    /**\n     * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than\n     * nodes with only inputs.\n     * @method updateExecutionOrder\n     */\n    LGraph.prototype.updateExecutionOrder = function() {\n        this._nodes_in_order = this.computeExecutionOrder(false);\n        this._nodes_executable = [];\n        for (var i = 0; i < this._nodes_in_order.length; ++i) {\n            if (this._nodes_in_order[i].onExecute) {\n                this._nodes_executable.push(this._nodes_in_order[i]);\n            }\n        }\n    };\n\n    //This is more internal, it computes the executable nodes in order and returns it\n    LGraph.prototype.computeExecutionOrder = function(\n        only_onExecute,\n        set_level\n    ) {\n        var L = [];\n        var S = [];\n        var M = {};\n        var visited_links = {}; //to avoid repeating links\n        var remaining_links = {}; //to a\n\n        //search for the nodes without inputs (starting nodes)\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            var node = this._nodes[i];\n            if (only_onExecute && !node.onExecute) {\n                continue;\n            }\n\n            M[node.id] = node; //add to pending nodes\n\n            var num = 0; //num of input connections\n            if (node.inputs) {\n                for (var j = 0, l2 = node.inputs.length; j < l2; j++) {\n                    if (node.inputs[j] && node.inputs[j].link != null) {\n                        num += 1;\n                    }\n                }\n            }\n\n            if (num == 0) {\n                //is a starting node\n                S.push(node);\n                if (set_level) {\n                    node._level = 1;\n                }\n            } //num of input links\n            else {\n                if (set_level) {\n                    node._level = 0;\n                }\n                remaining_links[node.id] = num;\n            }\n        }\n\n        while (true) {\n            if (S.length == 0) {\n                break;\n            }\n\n            //get an starting node\n            var node = S.shift();\n            L.push(node); //add to ordered list\n            delete M[node.id]; //remove from the pending nodes\n\n            if (!node.outputs) {\n                continue;\n            }\n\n            //for every output\n            for (var i = 0; i < node.outputs.length; i++) {\n                var output = node.outputs[i];\n                //not connected\n                if (\n                    output == null ||\n                    output.links == null ||\n                    output.links.length == 0\n                ) {\n                    continue;\n                }\n\n                //for every connection\n                for (var j = 0; j < output.links.length; j++) {\n                    var link_id = output.links[j];\n                    var link = this.links[link_id];\n                    if (!link) {\n                        continue;\n                    }\n\n                    //already visited link (ignore it)\n                    if (visited_links[link.id]) {\n                        continue;\n                    }\n\n                    var target_node = this.getNodeById(link.target_id);\n                    if (target_node == null) {\n                        visited_links[link.id] = true;\n                        continue;\n                    }\n\n                    if (\n                        set_level &&\n                        (!target_node._level ||\n                            target_node._level <= node._level)\n                    ) {\n                        target_node._level = node._level + 1;\n                    }\n\n                    visited_links[link.id] = true; //mark as visited\n                    remaining_links[target_node.id] -= 1; //reduce the number of links remaining\n                    if (remaining_links[target_node.id] == 0) {\n                        S.push(target_node);\n                    } //if no more links, then add to starters array\n                }\n            }\n        }\n\n        //the remaining ones (loops)\n        for (var i in M) {\n            L.push(M[i]);\n        }\n\n        if (L.length != this._nodes.length && LiteGraph.debug) {\n            console.warn(\"something went wrong, nodes missing\");\n        }\n\n        var l = L.length;\n\n        //save order number in the node\n        for (var i = 0; i < l; ++i) {\n            L[i].order = i;\n        }\n\n        //sort now by priority\n        L = L.sort(function(A, B) {\n            var Ap = A.constructor.priority || A.priority || 0;\n            var Bp = B.constructor.priority || B.priority || 0;\n            if (Ap == Bp) {\n                //if same priority, sort by order\n                return A.order - B.order;\n            }\n            return Ap - Bp; //sort by priority\n        });\n\n        //save order number in the node, again...\n        for (var i = 0; i < l; ++i) {\n            L[i].order = i;\n        }\n\n        return L;\n    };\n\n    /**\n     * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.\n     * It doesn't include the node itself\n     * @method getAncestors\n     * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution\n     */\n    LGraph.prototype.getAncestors = function(node) {\n        var ancestors = [];\n        var pending = [node];\n        var visited = {};\n\n        while (pending.length) {\n            var current = pending.shift();\n            if (!current.inputs) {\n                continue;\n            }\n            if (!visited[current.id] && current != node) {\n                visited[current.id] = true;\n                ancestors.push(current);\n            }\n\n            for (var i = 0; i < current.inputs.length; ++i) {\n                var input = current.getInputNode(i);\n                if (input && ancestors.indexOf(input) == -1) {\n                    pending.push(input);\n                }\n            }\n        }\n\n        ancestors.sort(function(a, b) {\n            return a.order - b.order;\n        });\n        return ancestors;\n    };\n\n    /**\n     * Positions every node in a more readable manner\n     * @method arrange\n     */\n    LGraph.prototype.arrange = function (margin, layout) {\n        margin = margin || 100;\n\n        const nodes = this.computeExecutionOrder(false, true);\n        const columns = [];\n        for (let i = 0; i < nodes.length; ++i) {\n            const node = nodes[i];\n            const col = node._level || 1;\n            if (!columns[col]) {\n                columns[col] = [];\n            }\n            columns[col].push(node);\n        }\n\n        let x = margin;\n\n        for (let i = 0; i < columns.length; ++i) {\n            const column = columns[i];\n            if (!column) {\n                continue;\n            }\n            let max_size = 100;\n            let y = margin + LiteGraph.NODE_TITLE_HEIGHT;\n            for (let j = 0; j < column.length; ++j) {\n                const node = column[j];\n                node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x;\n                node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y;\n                const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0;\n                if (node.size[max_size_index] > max_size) {\n                    max_size = node.size[max_size_index];\n                }\n                const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1;\n                y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT;\n            }\n            x += max_size + margin;\n        }\n\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * Returns the amount of time the graph has been running in milliseconds\n     * @method getTime\n     * @return {number} number of milliseconds the graph has been running\n     */\n    LGraph.prototype.getTime = function() {\n        return this.globaltime;\n    };\n\n    /**\n     * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant\n     * @method getFixedTime\n     * @return {number} number of milliseconds the graph has been running\n     */\n\n    LGraph.prototype.getFixedTime = function() {\n        return this.fixedtime;\n    };\n\n    /**\n     * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct\n     * if the nodes are using graphical actions\n     * @method getElapsedTime\n     * @return {number} number of milliseconds it took the last cycle\n     */\n\n    LGraph.prototype.getElapsedTime = function() {\n        return this.elapsed_time;\n    };\n\n    /**\n     * Sends an event to all the nodes, useful to trigger stuff\n     * @method sendEventToAllNodes\n     * @param {String} eventname the name of the event (function to be called)\n     * @param {Array} params parameters in array format\n     */\n    LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) {\n        mode = mode || LiteGraph.ALWAYS;\n\n        var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes;\n        if (!nodes) {\n            return;\n        }\n\n        for (var j = 0, l = nodes.length; j < l; ++j) {\n            var node = nodes[j];\n\n            if (\n                node.constructor === LiteGraph.Subgraph &&\n                eventname != \"onExecute\"\n            ) {\n                if (node.mode == mode) {\n                    node.sendEventToAllNodes(eventname, params, mode);\n                }\n                continue;\n            }\n\n            if (!node[eventname] || node.mode != mode) {\n                continue;\n            }\n            if (params === undefined) {\n                node[eventname]();\n            } else if (params && params.constructor === Array) {\n                node[eventname].apply(node, params);\n            } else {\n                node[eventname](params);\n            }\n        }\n    };\n\n    LGraph.prototype.sendActionToCanvas = function(action, params) {\n        if (!this.list_of_graphcanvas) {\n            return;\n        }\n\n        for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n            var c = this.list_of_graphcanvas[i];\n            if (c[action]) {\n                c[action].apply(c, params);\n            }\n        }\n    };\n\n    /**\n     * Adds a new node instance to this graph\n     * @method add\n     * @param {LGraphNode} node the instance of the node\n     */\n\n    LGraph.prototype.add = function(node, skip_compute_order) {\n        if (!node) {\n            return;\n        }\n\n        //groups\n        if (node.constructor === LGraphGroup) {\n            this._groups.push(node);\n            this.setDirtyCanvas(true);\n            this.change();\n            node.graph = this;\n            this._version++;\n            return;\n        }\n\n        //nodes\n        if (node.id != -1 && this._nodes_by_id[node.id] != null) {\n            console.warn(\n                \"LiteGraph: there is already a node with this ID, changing it\"\n            );\n            if (LiteGraph.use_uuids) {\n                node.id = LiteGraph.uuidv4();\n            }\n            else {\n                node.id = ++this.last_node_id;\n            }\n        }\n\n        if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) {\n            throw \"LiteGraph: max number of nodes in a graph reached\";\n        }\n\n        //give him an id\n        if (LiteGraph.use_uuids) {\n            if (node.id == null || node.id == -1)\n                node.id = LiteGraph.uuidv4();\n        }\n        else {\n            if (node.id == null || node.id == -1) {\n                node.id = ++this.last_node_id;\n            } else if (this.last_node_id < node.id) {\n                this.last_node_id = node.id;\n            }\n        }\n\n        node.graph = this;\n        this._version++;\n\n        this._nodes.push(node);\n        this._nodes_by_id[node.id] = node;\n\n        if (node.onAdded) {\n            node.onAdded(this);\n        }\n\n        if (this.config.align_to_grid) {\n            node.alignToGrid();\n        }\n\n        if (!skip_compute_order) {\n            this.updateExecutionOrder();\n        }\n\n        if (this.onNodeAdded) {\n            this.onNodeAdded(node);\n        }\n\n        this.setDirtyCanvas(true);\n        this.change();\n\n        return node; //to chain actions\n    };\n\n    /**\n     * Removes a node from the graph\n     * @method remove\n     * @param {LGraphNode} node the instance of the node\n     */\n\n    LGraph.prototype.remove = function(node) {\n        if (node.constructor === LiteGraph.LGraphGroup) {\n            var index = this._groups.indexOf(node);\n            if (index != -1) {\n                this._groups.splice(index, 1);\n            }\n            node.graph = null;\n            this._version++;\n            this.setDirtyCanvas(true, true);\n            this.change();\n            return;\n        }\n\n        if (this._nodes_by_id[node.id] == null) {\n            return;\n        } //not found\n\n        if (node.ignore_remove) {\n            return;\n        } //cannot be removed\n\n\t\tthis.beforeChange(); //sure? - almost sure is wrong\n\n        //disconnect inputs\n        if (node.inputs) {\n            for (var i = 0; i < node.inputs.length; i++) {\n                var slot = node.inputs[i];\n                if (slot.link != null) {\n                    node.disconnectInput(i);\n                }\n            }\n        }\n\n        //disconnect outputs\n        if (node.outputs) {\n            for (var i = 0; i < node.outputs.length; i++) {\n                var slot = node.outputs[i];\n                if (slot.links != null && slot.links.length) {\n                    node.disconnectOutput(i);\n                }\n            }\n        }\n\n        //node.id = -1; //why?\n\n        //callback\n        if (node.onRemoved) {\n            node.onRemoved();\n        }\n\n        node.graph = null;\n        this._version++;\n\n        //remove from canvas render\n        if (this.list_of_graphcanvas) {\n            for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n                var canvas = this.list_of_graphcanvas[i];\n                if (canvas.selected_nodes[node.id]) {\n                    delete canvas.selected_nodes[node.id];\n                }\n                if (canvas.node_dragged == node) {\n                    canvas.node_dragged = null;\n                }\n            }\n        }\n\n        //remove from containers\n        var pos = this._nodes.indexOf(node);\n        if (pos != -1) {\n            this._nodes.splice(pos, 1);\n        }\n        delete this._nodes_by_id[node.id];\n\n        if (this.onNodeRemoved) {\n            this.onNodeRemoved(node);\n        }\n\n\t\t//close panels\n\t\tthis.sendActionToCanvas(\"checkPanels\");\n\n        this.setDirtyCanvas(true, true);\n\t\tthis.afterChange(); //sure? - almost sure is wrong\n        this.change();\n\n        this.updateExecutionOrder();\n    };\n\n    /**\n     * Returns a node by its id.\n     * @method getNodeById\n     * @param {Number} id\n     */\n\n    LGraph.prototype.getNodeById = function(id) {\n        if (id == null) {\n            return null;\n        }\n        return this._nodes_by_id[id];\n    };\n\n    /**\n     * Returns a list of nodes that matches a class\n     * @method findNodesByClass\n     * @param {Class} classObject the class itself (not an string)\n     * @return {Array} a list with all the nodes of this type\n     */\n    LGraph.prototype.findNodesByClass = function(classObject, result) {\n        result = result || [];\n        result.length = 0;\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].constructor === classObject) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns a list of nodes that matches a type\n     * @method findNodesByType\n     * @param {String} type the name of the node type\n     * @return {Array} a list with all the nodes of this type\n     */\n    LGraph.prototype.findNodesByType = function(type, result) {\n        var type = type.toLowerCase();\n        result = result || [];\n        result.length = 0;\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].type.toLowerCase() == type) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns the first node that matches a name in its title\n     * @method findNodeByTitle\n     * @param {String} name the name of the node to search\n     * @return {Node} the node or null\n     */\n    LGraph.prototype.findNodeByTitle = function(title) {\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].title == title) {\n                return this._nodes[i];\n            }\n        }\n        return null;\n    };\n\n    /**\n     * Returns a list of nodes that matches a name\n     * @method findNodesByTitle\n     * @param {String} name the name of the node to search\n     * @return {Array} a list with all the nodes with this name\n     */\n    LGraph.prototype.findNodesByTitle = function(title) {\n        var result = [];\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            if (this._nodes[i].title == title) {\n                result.push(this._nodes[i]);\n            }\n        }\n        return result;\n    };\n\n    /**\n     * Returns the top-most node in this position of the canvas\n     * @method getNodeOnPos\n     * @param {number} x the x coordinate in canvas space\n     * @param {number} y the y coordinate in canvas space\n     * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph\n     * @return {LGraphNode} the node at this position or null\n     */\n    LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) {\n        nodes_list = nodes_list || this._nodes;\n\t\tvar nRet = null;\n        for (var i = nodes_list.length - 1; i >= 0; i--) {\n            var n = nodes_list[i];\n            if (n.isPointInside(x, y, margin)) {\n                // check for lesser interest nodes (TODO check for overlapping, use the top)\n\t\t\t\t/*if (typeof n == \"LGraphGroup\"){\n\t\t\t\t\tnRet = n;\n\t\t\t\t}else{*/\n\t\t\t\t\treturn n;\n\t\t\t\t/*}*/\n            }\n        }\n        return nRet;\n    };\n\n    /**\n     * Returns the top-most group in that position\n     * @method getGroupOnPos\n     * @param {number} x the x coordinate in canvas space\n     * @param {number} y the y coordinate in canvas space\n     * @return {LGraphGroup} the group or null\n     */\n    LGraph.prototype.getGroupOnPos = function(x, y) {\n        for (var i = this._groups.length - 1; i >= 0; i--) {\n            var g = this._groups[i];\n            if (g.isPointInside(x, y, 2, true)) {\n                return g;\n            }\n        }\n        return null;\n    };\n\n    /**\n     * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution\n     * this replaces the ones using the old version with the new version\n     * @method checkNodeTypes\n     */\n    LGraph.prototype.checkNodeTypes = function() {\n        var changes = false;\n        for (var i = 0; i < this._nodes.length; i++) {\n            var node = this._nodes[i];\n            var ctor = LiteGraph.registered_node_types[node.type];\n            if (node.constructor == ctor) {\n                continue;\n            }\n            console.log(\"node being replaced by newer version: \" + node.type);\n            var newnode = LiteGraph.createNode(node.type);\n            changes = true;\n            this._nodes[i] = newnode;\n            newnode.configure(node.serialize());\n            newnode.graph = this;\n            this._nodes_by_id[newnode.id] = newnode;\n            if (node.inputs) {\n                newnode.inputs = node.inputs.concat();\n            }\n            if (node.outputs) {\n                newnode.outputs = node.outputs.concat();\n            }\n        }\n        this.updateExecutionOrder();\n    };\n\n    // ********** GLOBALS *****************\n\n    LGraph.prototype.onAction = function(action, param, options) {\n        this._input_nodes = this.findNodesByClass(\n            LiteGraph.GraphInput,\n            this._input_nodes\n        );\n        for (var i = 0; i < this._input_nodes.length; ++i) {\n            var node = this._input_nodes[i];\n            if (node.properties.name != action) {\n                continue;\n            }\n            //wrap node.onAction(action, param);\n            node.actionDo(action, param, options);\n            break;\n        }\n    };\n\n    LGraph.prototype.trigger = function(action, param) {\n        if (this.onTrigger) {\n            this.onTrigger(action, param);\n        }\n    };\n\n    /**\n     * Tell this graph it has a global graph input of this type\n     * @method addGlobalInput\n     * @param {String} name\n     * @param {String} type\n     * @param {*} value [optional]\n     */\n    LGraph.prototype.addInput = function(name, type, value) {\n        var input = this.inputs[name];\n        if (input) {\n            //already exist\n            return;\n        }\n\n\t\tthis.beforeChange();\n        this.inputs[name] = { name: name, type: type, value: value };\n        this._version++;\n\t\tthis.afterChange();\n\n        if (this.onInputAdded) {\n            this.onInputAdded(name, type);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Assign a data to the global graph input\n     * @method setGlobalInputData\n     * @param {String} name\n     * @param {*} data\n     */\n    LGraph.prototype.setInputData = function(name, data) {\n        var input = this.inputs[name];\n        if (!input) {\n            return;\n        }\n        input.value = data;\n    };\n\n    /**\n     * Returns the current value of a global graph input\n     * @method getInputData\n     * @param {String} name\n     * @return {*} the data\n     */\n    LGraph.prototype.getInputData = function(name) {\n        var input = this.inputs[name];\n        if (!input) {\n            return null;\n        }\n        return input.value;\n    };\n\n    /**\n     * Changes the name of a global graph input\n     * @method renameInput\n     * @param {String} old_name\n     * @param {String} new_name\n     */\n    LGraph.prototype.renameInput = function(old_name, name) {\n        if (name == old_name) {\n            return;\n        }\n\n        if (!this.inputs[old_name]) {\n            return false;\n        }\n\n        if (this.inputs[name]) {\n            console.error(\"there is already one input with that name\");\n            return false;\n        }\n\n        this.inputs[name] = this.inputs[old_name];\n        delete this.inputs[old_name];\n        this._version++;\n\n        if (this.onInputRenamed) {\n            this.onInputRenamed(old_name, name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Changes the type of a global graph input\n     * @method changeInputType\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.changeInputType = function(name, type) {\n        if (!this.inputs[name]) {\n            return false;\n        }\n\n        if (\n            this.inputs[name].type &&\n            String(this.inputs[name].type).toLowerCase() ==\n                String(type).toLowerCase()\n        ) {\n            return;\n        }\n\n        this.inputs[name].type = type;\n        this._version++;\n        if (this.onInputTypeChanged) {\n            this.onInputTypeChanged(name, type);\n        }\n    };\n\n    /**\n     * Removes a global graph input\n     * @method removeInput\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.removeInput = function(name) {\n        if (!this.inputs[name]) {\n            return false;\n        }\n\n        delete this.inputs[name];\n        this._version++;\n\n        if (this.onInputRemoved) {\n            this.onInputRemoved(name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n        return true;\n    };\n\n    /**\n     * Creates a global graph output\n     * @method addOutput\n     * @param {String} name\n     * @param {String} type\n     * @param {*} value\n     */\n    LGraph.prototype.addOutput = function(name, type, value) {\n        this.outputs[name] = { name: name, type: type, value: value };\n        this._version++;\n\n        if (this.onOutputAdded) {\n            this.onOutputAdded(name, type);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Assign a data to the global output\n     * @method setOutputData\n     * @param {String} name\n     * @param {String} value\n     */\n    LGraph.prototype.setOutputData = function(name, value) {\n        var output = this.outputs[name];\n        if (!output) {\n            return;\n        }\n        output.value = value;\n    };\n\n    /**\n     * Returns the current value of a global graph output\n     * @method getOutputData\n     * @param {String} name\n     * @return {*} the data\n     */\n    LGraph.prototype.getOutputData = function(name) {\n        var output = this.outputs[name];\n        if (!output) {\n            return null;\n        }\n        return output.value;\n    };\n\n    /**\n     * Renames a global graph output\n     * @method renameOutput\n     * @param {String} old_name\n     * @param {String} new_name\n     */\n    LGraph.prototype.renameOutput = function(old_name, name) {\n        if (!this.outputs[old_name]) {\n            return false;\n        }\n\n        if (this.outputs[name]) {\n            console.error(\"there is already one output with that name\");\n            return false;\n        }\n\n        this.outputs[name] = this.outputs[old_name];\n        delete this.outputs[old_name];\n        this._version++;\n\n        if (this.onOutputRenamed) {\n            this.onOutputRenamed(old_name, name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n    };\n\n    /**\n     * Changes the type of a global graph output\n     * @method changeOutputType\n     * @param {String} name\n     * @param {String} type\n     */\n    LGraph.prototype.changeOutputType = function(name, type) {\n        if (!this.outputs[name]) {\n            return false;\n        }\n\n        if (\n            this.outputs[name].type &&\n            String(this.outputs[name].type).toLowerCase() ==\n                String(type).toLowerCase()\n        ) {\n            return;\n        }\n\n        this.outputs[name].type = type;\n        this._version++;\n        if (this.onOutputTypeChanged) {\n            this.onOutputTypeChanged(name, type);\n        }\n    };\n\n    /**\n     * Removes a global graph output\n     * @method removeOutput\n     * @param {String} name\n     */\n    LGraph.prototype.removeOutput = function(name) {\n        if (!this.outputs[name]) {\n            return false;\n        }\n        delete this.outputs[name];\n        this._version++;\n\n        if (this.onOutputRemoved) {\n            this.onOutputRemoved(name);\n        }\n\n        if (this.onInputsOutputsChange) {\n            this.onInputsOutputsChange();\n        }\n        return true;\n    };\n\n    LGraph.prototype.triggerInput = function(name, value) {\n        var nodes = this.findNodesByTitle(name);\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].onTrigger(value);\n        }\n    };\n\n    LGraph.prototype.setCallback = function(name, func) {\n        var nodes = this.findNodesByTitle(name);\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].setTrigger(func);\n        }\n    };\n\n\t//used for undo, called before any change is made to the graph\n    LGraph.prototype.beforeChange = function(info) {\n        if (this.onBeforeChange) {\n            this.onBeforeChange(this,info);\n        }\n        this.sendActionToCanvas(\"onBeforeChange\", this);\n    };\n\n\t//used to resend actions, called after any change is made to the graph\n    LGraph.prototype.afterChange = function(info) {\n        if (this.onAfterChange) {\n            this.onAfterChange(this,info);\n        }\n        this.sendActionToCanvas(\"onAfterChange\", this);\n    };\n\n    LGraph.prototype.connectionChange = function(node, link_info) {\n        this.updateExecutionOrder();\n        if (this.onConnectionChange) {\n            this.onConnectionChange(node);\n        }\n        this._version++;\n        this.sendActionToCanvas(\"onConnectionChange\");\n    };\n\n    /**\n     * returns if the graph is in live mode\n     * @method isLive\n     */\n\n    LGraph.prototype.isLive = function() {\n        if (!this.list_of_graphcanvas) {\n            return false;\n        }\n\n        for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {\n            var c = this.list_of_graphcanvas[i];\n            if (c.live_mode) {\n                return true;\n            }\n        }\n        return false;\n    };\n\n    /**\n     * clears the triggered slot animation in all links (stop visual animation)\n     * @method clearTriggeredSlots\n     */\n    LGraph.prototype.clearTriggeredSlots = function() {\n        for (var i in this.links) {\n            var link_info = this.links[i];\n            if (!link_info) {\n                continue;\n            }\n            if (link_info._last_time) {\n                link_info._last_time = 0;\n            }\n        }\n    };\n\n    /* Called when something visually changed (not the graph!) */\n    LGraph.prototype.change = function() {\n        if (LiteGraph.debug) {\n            console.log(\"Graph changed\");\n        }\n        this.sendActionToCanvas(\"setDirty\", [true, true]);\n        if (this.on_change) {\n            this.on_change(this);\n        }\n    };\n\n    LGraph.prototype.setDirtyCanvas = function(fg, bg) {\n        this.sendActionToCanvas(\"setDirty\", [fg, bg]);\n    };\n\n    /**\n     * Destroys a link\n     * @method removeLink\n     * @param {Number} link_id\n     */\n    LGraph.prototype.removeLink = function(link_id) {\n        var link = this.links[link_id];\n        if (!link) {\n            return;\n        }\n        var node = this.getNodeById(link.target_id);\n        if (node) {\n            node.disconnectInput(link.target_slot);\n        }\n    };\n\n    //save and recover app state ***************************************\n    /**\n     * Creates a Object containing all the info about this graph, it can be serialized\n     * @method serialize\n     * @return {Object} value of the node\n     */\n    LGraph.prototype.serialize = function() {\n        var nodes_info = [];\n        for (var i = 0, l = this._nodes.length; i < l; ++i) {\n            nodes_info.push(this._nodes[i].serialize());\n        }\n\n        //pack link info into a non-verbose format\n        var links = [];\n        for (var i in this.links) {\n            //links is an OBJECT\n            var link = this.links[i];\n            if (!link.serialize) {\n                //weird bug I havent solved yet\n                console.warn(\n                    \"weird LLink bug, link info is not a LLink but a regular object\"\n                );\n                var link2 = new LLink();\n                for (var j in link) { \n                    link2[j] = link[j];\n                }\n                this.links[i] = link2;\n                link = link2;\n            }\n\n            links.push(link.serialize());\n        }\n\n        var groups_info = [];\n        for (var i = 0; i < this._groups.length; ++i) {\n            groups_info.push(this._groups[i].serialize());\n        }\n\n        var data = {\n            last_node_id: this.last_node_id,\n            last_link_id: this.last_link_id,\n            nodes: nodes_info,\n            links: links,\n            groups: groups_info,\n            config: this.config,\n\t\t\textra: this.extra,\n            version: LiteGraph.VERSION\n        };\n\n\t\tif(this.onSerialize)\n\t\t\tthis.onSerialize(data);\n\n        return data;\n    };\n\n    /**\n     * Configure a graph from a JSON string\n     * @method configure\n     * @param {String} str configure a graph from a JSON string\n     * @param {Boolean} returns if there was any error parsing\n     */\n    LGraph.prototype.configure = function(data, keep_old) {\n        if (!data) {\n            return;\n        }\n\n        if (!keep_old) {\n            this.clear();\n        }\n\n        var nodes = data.nodes;\n\n        //decode links info (they are very verbose)\n        if (data.links && data.links.constructor === Array) {\n            var links = [];\n            for (var i = 0; i < data.links.length; ++i) {\n                var link_data = data.links[i];\n\t\t\t\tif(!link_data) //weird bug\n\t\t\t\t{\n\t\t\t\t\tconsole.warn(\"serialized graph link data contains errors, skipping.\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n                var link = new LLink();\n                link.configure(link_data);\n                links[link.id] = link;\n            }\n            data.links = links;\n        }\n\n        //copy all stored fields\n        for (var i in data) {\n\t\t\tif(i == \"nodes\" || i == \"groups\" ) //links must be accepted\n\t\t\t\tcontinue;\n            this[i] = data[i];\n        }\n\n        var error = false;\n\n        //create nodes\n        this._nodes = [];\n        if (nodes) {\n            for (var i = 0, l = nodes.length; i < l; ++i) {\n                var n_info = nodes[i]; //stored info\n                var node = LiteGraph.createNode(n_info.type, n_info.title);\n                if (!node) {\n                    if (LiteGraph.debug) {\n                        console.log(\n                            \"Node not found or has errors: \" + n_info.type\n                        );\n                    }\n\n                    //in case of error we create a replacement node to avoid losing info\n                    node = new LGraphNode();\n                    node.last_serialization = n_info;\n                    node.has_errors = true;\n                    error = true;\n                    //continue;\n                }\n\n                node.id = n_info.id; //id it or it will create a new id\n                this.add(node, true); //add before configure, otherwise configure cannot create links\n            }\n\n            //configure nodes afterwards so they can reach each other\n            for (var i = 0, l = nodes.length; i < l; ++i) {\n                var n_info = nodes[i];\n                var node = this.getNodeById(n_info.id);\n                if (node) {\n                    node.configure(n_info);\n                }\n            }\n        }\n\n        //groups\n        this._groups.length = 0;\n        if (data.groups) {\n            for (var i = 0; i < data.groups.length; ++i) {\n                var group = new LiteGraph.LGraphGroup();\n                group.configure(data.groups[i]);\n                this.add(group);\n            }\n        }\n\n        this.updateExecutionOrder();\n\n\t\tthis.extra = data.extra || {};\n\n\t\tif(this.onConfigure)\n\t\t\tthis.onConfigure(data);\n\n        this._version++;\n        this.setDirtyCanvas(true, true);\n        return error;\n    };\n\n    LGraph.prototype.load = function(url, callback) {\n        var that = this;\n\n\t\t//from file\n\t\tif(url.constructor === File || url.constructor === Blob)\n\t\t{\n\t\t\tvar reader = new FileReader();\n\t\t\treader.addEventListener('load', function(event) {\n\t\t\t\tvar data = JSON.parse(event.target.result);\n\t\t\t\tthat.configure(data);\n\t\t\t\tif(callback)\n\t\t\t\t\tcallback();\n\t\t\t});\n\t\t\t\n\t\t\treader.readAsText(url);\n\t\t\treturn;\n\t\t}\n\n\t\t//is a string, then an URL\n        var req = new XMLHttpRequest();\n        req.open(\"GET\", url, true);\n        req.send(null);\n        req.onload = function(oEvent) {\n            if (req.status !== 200) {\n                console.error(\"Error loading graph:\", req.status, req.response);\n                return;\n            }\n            var data = JSON.parse( req.response );\n            that.configure(data);\n\t\t\tif(callback)\n\t\t\t\tcallback();\n        };\n        req.onerror = function(err) {\n            console.error(\"Error loading graph:\", err);\n        };\n    };\n\n    LGraph.prototype.onNodeTrace = function(node, msg, color) {\n        //TODO\n    };\n\n    //this is the class in charge of storing link information\n    function LLink(id, type, origin_id, origin_slot, target_id, target_slot) {\n        this.id = id;\n        this.type = type;\n        this.origin_id = origin_id;\n        this.origin_slot = origin_slot;\n        this.target_id = target_id;\n        this.target_slot = target_slot;\n\n        this._data = null;\n        this._pos = new Float32Array(2); //center\n    }\n\n    LLink.prototype.configure = function(o) {\n        if (o.constructor === Array) {\n            this.id = o[0];\n            this.origin_id = o[1];\n            this.origin_slot = o[2];\n            this.target_id = o[3];\n            this.target_slot = o[4];\n            this.type = o[5];\n        } else {\n            this.id = o.id;\n            this.type = o.type;\n            this.origin_id = o.origin_id;\n            this.origin_slot = o.origin_slot;\n            this.target_id = o.target_id;\n            this.target_slot = o.target_slot;\n        }\n    };\n\n    LLink.prototype.serialize = function() {\n        return [\n            this.id,\n            this.origin_id,\n            this.origin_slot,\n            this.target_id,\n            this.target_slot,\n            this.type\n        ];\n    };\n\n    LiteGraph.LLink = LLink;\n\n    // *************************************************************\n    //   Node CLASS                                          *******\n    // *************************************************************\n\n    /*\n\ttitle: string\n\tpos: [x,y]\n\tsize: [x,y]\n\n\tinput|output: every connection\n\t\t+  { name:string, type:string, pos: [x,y]=Optional, direction: \"input\"|\"output\", links: Array });\n\n\tgeneral properties:\n\t\t+ clip_area: if you render outside the node, it will be clipped\n\t\t+ unsafe_execution: not allowed for safe execution\n\t\t+ skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected\n\t\t+ resizable: if set to false it wont be resizable with the mouse\n\t\t+ horizontal: slots are distributed horizontally\n\t\t+ widgets_start_y: widgets start at y distance from the top of the node\n\t\n\tflags object:\n\t\t+ collapsed: if it is collapsed\n\n\tsupported callbacks:\n\t\t+ onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading)\n\t\t+ onRemoved: when removed from graph\n\t\t+ onStart:\twhen the graph starts playing\n\t\t+ onStop:\twhen the graph stops playing\n\t\t+ onDrawForeground: render the inside widgets inside the node\n\t\t+ onDrawBackground: render the background area inside the node (only in edit mode)\n\t\t+ onMouseDown\n\t\t+ onMouseMove\n\t\t+ onMouseUp\n\t\t+ onMouseEnter\n\t\t+ onMouseLeave\n\t\t+ onExecute: execute the node\n\t\t+ onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour)\n\t\t+ onGetInputs: returns an array of possible inputs\n\t\t+ onGetOutputs: returns an array of possible outputs\n\t\t+ onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h])\n\t\t+ onDblClick: double clicked in the node\n\t\t+ onInputDblClick: input slot double clicked (can be used to automatically create a node connected)\n\t\t+ onOutputDblClick: output slot double clicked (can be used to automatically create a node connected)\n\t\t+ onConfigure: called after the node has been configured\n\t\t+ onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data)\n\t\t+ onSelected\n\t\t+ onDeselected\n\t\t+ onDropItem : DOM item dropped over the node\n\t\t+ onDropFile : file dropped over the node\n\t\t+ onConnectInput : if returns false the incoming connection will be canceled\n\t\t+ onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info )\n\t\t+ onAction: action slot triggered\n\t\t+ getExtraMenuOptions: to add option to context menu\n*/\n\n    /**\n     * Base Class for all the node type classes\n     * @class LGraphNode\n     * @param {String} name a name for the node\n     */\n\n    function LGraphNode(title) {\n        this._ctor(title);\n    }\n\n    global.LGraphNode = LiteGraph.LGraphNode = LGraphNode;\n\n    LGraphNode.prototype._ctor = function(title) {\n        this.title = title || \"Unnamed\";\n        this.size = [LiteGraph.NODE_WIDTH, 60];\n        this.graph = null;\n\n        this._pos = new Float32Array(10, 10);\n\n        Object.defineProperty(this, \"pos\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._pos[0] = v[0];\n                this._pos[1] = v[1];\n            },\n            get: function() {\n                return this._pos;\n            },\n            enumerable: true\n        });\n\n        if (LiteGraph.use_uuids) {\n            this.id = LiteGraph.uuidv4();\n        }\n        else {\n            this.id = -1; //not know till not added\n        }\n        this.type = null;\n\n        //inputs available: array of inputs\n        this.inputs = [];\n        this.outputs = [];\n        this.connections = [];\n\n        //local data\n        this.properties = {}; //for the values\n        this.properties_info = []; //for the info\n\n        this.flags = {};\n    };\n\n    /**\n     * configure a node from an object containing the serialized info\n     * @method configure\n     */\n    LGraphNode.prototype.configure = function(info) {\n        if (this.graph) {\n            this.graph._version++;\n        }\n        for (var j in info) {\n            if (j == \"properties\") {\n                //i don't want to clone properties, I want to reuse the old container\n                for (var k in info.properties) {\n                    this.properties[k] = info.properties[k];\n                    if (this.onPropertyChanged) {\n                        this.onPropertyChanged( k, info.properties[k] );\n                    }\n                }\n                continue;\n            }\n\n            if (info[j] == null) {\n                continue;\n            } else if (typeof info[j] == \"object\") {\n                //object\n                if (this[j] && this[j].configure) {\n                    this[j].configure(info[j]);\n                } else {\n                    this[j] = LiteGraph.cloneObject(info[j], this[j]);\n                }\n            } //value\n            else {\n                this[j] = info[j];\n            }\n        }\n\n        if (!info.title) {\n            this.title = this.constructor.title;\n        }\n\n\t\tif (this.inputs) {\n\t\t\tfor (var i = 0; i < this.inputs.length; ++i) {\n\t\t\t\tvar input = this.inputs[i];\n\t\t\t\tvar link_info = this.graph ? this.graph.links[input.link] : null;\n\t\t\t\tif (this.onConnectionsChange)\n\t\t\t\t\tthis.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated\n\n\t\t\t\tif( this.onInputAdded )\n\t\t\t\t\tthis.onInputAdded(input);\n\n\t\t\t}\n\t\t}\n\n\t\tif (this.outputs) {\n\t\t\tfor (var i = 0; i < this.outputs.length; ++i) {\n\t\t\t\tvar output = this.outputs[i];\n\t\t\t\tif (!output.links) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfor (var j = 0; j < output.links.length; ++j) {\n\t\t\t\t\tvar link_info = this.graph \t? this.graph.links[output.links[j]] : null;\n\t\t\t\t\tif (this.onConnectionsChange)\n\t\t\t\t\t\tthis.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated\n\t\t\t\t}\n\n\t\t\t\tif( this.onOutputAdded )\n\t\t\t\t\tthis.onOutputAdded(output);\n\t\t\t}\n        }\n\n\t\tif( this.widgets )\n\t\t{\n\t\t\tfor (var i = 0; i < this.widgets.length; ++i)\n\t\t\t{\n\t\t\t\tvar w = this.widgets[i];\n\t\t\t\tif(!w)\n\t\t\t\t\tcontinue;\n\t\t\t\tif(w.options && w.options.property && (this.properties[ w.options.property ] != undefined))\n\t\t\t\t\tw.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) );\n\t\t\t}\n\t\t\tif (info.widgets_values) {\n\t\t\t\tfor (var i = 0; i < info.widgets_values.length; ++i) {\n\t\t\t\t\tif (this.widgets[i]) {\n\t\t\t\t\t\tthis.widgets[i].value = info.widgets_values[i];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n        if (this.onConfigure) {\n            this.onConfigure(info);\n        }\n    };\n\n    /**\n     * serialize the content\n     * @method serialize\n     */\n\n    LGraphNode.prototype.serialize = function() {\n        //create serialization object\n        var o = {\n            id: this.id,\n            type: this.type,\n            pos: this.pos,\n            size: this.size,\n            flags: LiteGraph.cloneObject(this.flags),\n\t\t\torder: this.order,\n            mode: this.mode\n        };\n\n        //special case for when there were errors\n        if (this.constructor === LGraphNode && this.last_serialization) {\n            return this.last_serialization;\n        }\n\n        if (this.inputs) {\n            o.inputs = this.inputs;\n        }\n\n        if (this.outputs) {\n            //clear outputs last data (because data in connections is never serialized but stored inside the outputs info)\n            for (var i = 0; i < this.outputs.length; i++) {\n                delete this.outputs[i]._data;\n            }\n            o.outputs = this.outputs;\n        }\n\n        if (this.title && this.title != this.constructor.title) {\n            o.title = this.title;\n        }\n\n        if (this.properties) {\n            o.properties = LiteGraph.cloneObject(this.properties);\n        }\n\n        if (this.widgets && this.serialize_widgets) {\n            o.widgets_values = [];\n            for (var i = 0; i < this.widgets.length; ++i) {\n\t\t\t\tif(this.widgets[i])\n\t                o.widgets_values[i] = this.widgets[i].value;\n\t\t\t\telse\n\t\t\t\t\to.widgets_values[i] = null;\n            }\n        }\n\n        if (!o.type) {\n            o.type = this.constructor.type;\n        }\n\n        if (this.color) {\n            o.color = this.color;\n        }\n        if (this.bgcolor) {\n            o.bgcolor = this.bgcolor;\n        }\n        if (this.boxcolor) {\n            o.boxcolor = this.boxcolor;\n        }\n        if (this.shape) {\n            o.shape = this.shape;\n        }\n\n        if (this.onSerialize) {\n            if (this.onSerialize(o)) {\n                console.warn(\n                    \"node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter\"\n                );\n            }\n        }\n\n        return o;\n    };\n\n    /* Creates a clone of this node */\n    LGraphNode.prototype.clone = function() {\n        var node = LiteGraph.createNode(this.type);\n        if (!node) {\n            return null;\n        }\n\n        //we clone it because serialize returns shared containers\n        var data = LiteGraph.cloneObject(this.serialize());\n\n        //remove links\n        if (data.inputs) {\n            for (var i = 0; i < data.inputs.length; ++i) {\n                data.inputs[i].link = null;\n            }\n        }\n\n        if (data.outputs) {\n            for (var i = 0; i < data.outputs.length; ++i) {\n                if (data.outputs[i].links) {\n                    data.outputs[i].links.length = 0;\n                }\n            }\n        }\n\n        delete data[\"id\"];\n\n        if (LiteGraph.use_uuids) {\n            data[\"id\"] = LiteGraph.uuidv4()\n        }\n\n        //remove links\n        node.configure(data);\n\n        return node;\n    };\n\n    /**\n     * serialize and stringify\n     * @method toString\n     */\n\n    LGraphNode.prototype.toString = function() {\n        return JSON.stringify(this.serialize());\n    };\n    //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph\n\n    /**\n     * get the title string\n     * @method getTitle\n     */\n\n    LGraphNode.prototype.getTitle = function() {\n        return this.title || this.constructor.title;\n    };\n\n    /**\n     * sets the value of a property\n     * @method setProperty\n     * @param {String} name\n     * @param {*} value\n     */\n    LGraphNode.prototype.setProperty = function(name, value) {\n        if (!this.properties) {\n            this.properties = {};\n        }\n\t\tif( value === this.properties[name] )\n\t\t\treturn;\n\t\tvar prev_value = this.properties[name];\n        this.properties[name] = value;\n        if (this.onPropertyChanged) {\n            if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change\n\t\t\t\tthis.properties[name] = prev_value;\n        }\n\t\tif(this.widgets) //widgets could be linked to properties\n\t\t\tfor(var i = 0; i < this.widgets.length; ++i)\n\t\t\t{\n\t\t\t\tvar w = this.widgets[i];\n\t\t\t\tif(!w)\n\t\t\t\t\tcontinue;\n\t\t\t\tif(w.options.property == name)\n\t\t\t\t{\n\t\t\t\t\tw.value = value;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n    };\n\n    // Execution *************************\n    /**\n     * sets the output data\n     * @method setOutputData\n     * @param {number} slot\n     * @param {*} data\n     */\n    LGraphNode.prototype.setOutputData = function(slot, data) {\n        if (!this.outputs) {\n            return;\n        }\n\n        //this maybe slow and a niche case\n        //if(slot && slot.constructor === String)\n        //\tslot = this.findOutputSlot(slot);\n\n        if (slot == -1 || slot >= this.outputs.length) {\n            return;\n        }\n\n        var output_info = this.outputs[slot];\n        if (!output_info) {\n            return;\n        }\n\n        //store data in the output itself in case we want to debug\n        output_info._data = data;\n\n        //if there are connections, pass the data to the connections\n        if (this.outputs[slot].links) {\n            for (var i = 0; i < this.outputs[slot].links.length; i++) {\n                var link_id = this.outputs[slot].links[i];\n\t\t\t\tvar link = this.graph.links[link_id];\n\t\t\t\tif(link)\n\t\t\t\t\tlink.data = data;\n            }\n        }\n    };\n\n    /**\n     * sets the output data type, useful when you want to be able to overwrite the data type\n     * @method setOutputDataType\n     * @param {number} slot\n     * @param {String} datatype\n     */\n    LGraphNode.prototype.setOutputDataType = function(slot, type) {\n        if (!this.outputs) {\n            return;\n        }\n        if (slot == -1 || slot >= this.outputs.length) {\n            return;\n        }\n        var output_info = this.outputs[slot];\n        if (!output_info) {\n            return;\n        }\n        //store data in the output itself in case we want to debug\n        output_info.type = type;\n\n        //if there are connections, pass the data to the connections\n        if (this.outputs[slot].links) {\n            for (var i = 0; i < this.outputs[slot].links.length; i++) {\n                var link_id = this.outputs[slot].links[i];\n                this.graph.links[link_id].type = type;\n            }\n        }\n    };\n\n    /**\n     * Retrieves the input data (data traveling through the connection) from one slot\n     * @method getInputData\n     * @param {number} slot\n     * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link\n     * @return {*} data or if it is not connected returns undefined\n     */\n    LGraphNode.prototype.getInputData = function(slot, force_update) {\n        if (!this.inputs) {\n            return;\n        } //undefined;\n\n        if (slot >= this.inputs.length || this.inputs[slot].link == null) {\n            return;\n        }\n\n        var link_id = this.inputs[slot].link;\n        var link = this.graph.links[link_id];\n        if (!link) {\n            //bug: weird case but it happens sometimes\n            return null;\n        }\n\n        if (!force_update) {\n            return link.data;\n        }\n\n        //special case: used to extract data from the incoming connection before the graph has been executed\n        var node = this.graph.getNodeById(link.origin_id);\n        if (!node) {\n            return link.data;\n        }\n\n        if (node.updateOutputData) {\n            node.updateOutputData(link.origin_slot);\n        } else if (node.onExecute) {\n            node.onExecute();\n        }\n\n        return link.data;\n    };\n\n    /**\n     * Retrieves the input data type (in case this supports multiple input types)\n     * @method getInputDataType\n     * @param {number} slot\n     * @return {String} datatype in string format\n     */\n    LGraphNode.prototype.getInputDataType = function(slot) {\n        if (!this.inputs) {\n            return null;\n        } //undefined;\n\n        if (slot >= this.inputs.length || this.inputs[slot].link == null) {\n            return null;\n        }\n        var link_id = this.inputs[slot].link;\n        var link = this.graph.links[link_id];\n        if (!link) {\n            //bug: weird case but it happens sometimes\n            return null;\n        }\n        var node = this.graph.getNodeById(link.origin_id);\n        if (!node) {\n            return link.type;\n        }\n        var output_info = node.outputs[link.origin_slot];\n        if (output_info) {\n            return output_info.type;\n        }\n        return null;\n    };\n\n    /**\n     * Retrieves the input data from one slot using its name instead of slot number\n     * @method getInputDataByName\n     * @param {String} slot_name\n     * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link\n     * @return {*} data or if it is not connected returns null\n     */\n    LGraphNode.prototype.getInputDataByName = function(\n        slot_name,\n        force_update\n    ) {\n        var slot = this.findInputSlot(slot_name);\n        if (slot == -1) {\n            return null;\n        }\n        return this.getInputData(slot, force_update);\n    };\n\n    /**\n     * tells you if there is a connection in one input slot\n     * @method isInputConnected\n     * @param {number} slot\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isInputConnected = function(slot) {\n        if (!this.inputs) {\n            return false;\n        }\n        return slot < this.inputs.length && this.inputs[slot].link != null;\n    };\n\n    /**\n     * tells you info about an input connection (which node, type, etc)\n     * @method getInputInfo\n     * @param {number} slot\n     * @return {Object} object or null { link: id, name: string, type: string or 0 }\n     */\n    LGraphNode.prototype.getInputInfo = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot < this.inputs.length) {\n            return this.inputs[slot];\n        }\n        return null;\n    };\n\n    /**\n     * Returns the link info in the connection of an input slot\n     * @method getInputLink\n     * @param {number} slot\n     * @return {LLink} object or null\n     */\n    LGraphNode.prototype.getInputLink = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot < this.inputs.length) {\n            var slot_info = this.inputs[slot];\n\t\t\treturn this.graph.links[ slot_info.link ];\n        }\n        return null;\n    };\n\n    /**\n     * returns the node connected in the input slot\n     * @method getInputNode\n     * @param {number} slot\n     * @return {LGraphNode} node or null\n     */\n    LGraphNode.prototype.getInputNode = function(slot) {\n        if (!this.inputs) {\n            return null;\n        }\n        if (slot >= this.inputs.length) {\n            return null;\n        }\n        var input = this.inputs[slot];\n        if (!input || input.link === null) {\n            return null;\n        }\n        var link_info = this.graph.links[input.link];\n        if (!link_info) {\n            return null;\n        }\n        return this.graph.getNodeById(link_info.origin_id);\n    };\n\n    /**\n     * returns the value of an input with this name, otherwise checks if there is a property with that name\n     * @method getInputOrProperty\n     * @param {string} name\n     * @return {*} value\n     */\n    LGraphNode.prototype.getInputOrProperty = function(name) {\n        if (!this.inputs || !this.inputs.length) {\n            return this.properties ? this.properties[name] : null;\n        }\n\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            var input_info = this.inputs[i];\n            if (name == input_info.name && input_info.link != null) {\n                var link = this.graph.links[input_info.link];\n                if (link) {\n                    return link.data;\n                }\n            }\n        }\n        return this.properties[name];\n    };\n\n    /**\n     * tells you the last output data that went in that slot\n     * @method getOutputData\n     * @param {number} slot\n     * @return {Object}  object or null\n     */\n    LGraphNode.prototype.getOutputData = function(slot) {\n        if (!this.outputs) {\n            return null;\n        }\n        if (slot >= this.outputs.length) {\n            return null;\n        }\n\n        var info = this.outputs[slot];\n        return info._data;\n    };\n\n    /**\n     * tells you info about an output connection (which node, type, etc)\n     * @method getOutputInfo\n     * @param {number} slot\n     * @return {Object}  object or null { name: string, type: string, links: [ ids of links in number ] }\n     */\n    LGraphNode.prototype.getOutputInfo = function(slot) {\n        if (!this.outputs) {\n            return null;\n        }\n        if (slot < this.outputs.length) {\n            return this.outputs[slot];\n        }\n        return null;\n    };\n\n    /**\n     * tells you if there is a connection in one output slot\n     * @method isOutputConnected\n     * @param {number} slot\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isOutputConnected = function(slot) {\n        if (!this.outputs) {\n            return false;\n        }\n        return (\n            slot < this.outputs.length &&\n            this.outputs[slot].links &&\n            this.outputs[slot].links.length\n        );\n    };\n\n    /**\n     * tells you if there is any connection in the output slots\n     * @method isAnyOutputConnected\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isAnyOutputConnected = function() {\n        if (!this.outputs) {\n            return false;\n        }\n        for (var i = 0; i < this.outputs.length; ++i) {\n            if (this.outputs[i].links && this.outputs[i].links.length) {\n                return true;\n            }\n        }\n        return false;\n    };\n\n    /**\n     * retrieves all the nodes connected to this output slot\n     * @method getOutputNodes\n     * @param {number} slot\n     * @return {array}\n     */\n    LGraphNode.prototype.getOutputNodes = function(slot) {\n        if (!this.outputs || this.outputs.length == 0) {\n            return null;\n        }\n\n        if (slot >= this.outputs.length) {\n            return null;\n        }\n\n        var output = this.outputs[slot];\n        if (!output.links || output.links.length == 0) {\n            return null;\n        }\n\n        var r = [];\n        for (var i = 0; i < output.links.length; i++) {\n            var link_id = output.links[i];\n            var link = this.graph.links[link_id];\n            if (link) {\n                var target_node = this.graph.getNodeById(link.target_id);\n                if (target_node) {\n                    r.push(target_node);\n                }\n            }\n        }\n        return r;\n    };\n\n    LGraphNode.prototype.addOnTriggerInput = function(){\n        var trigS = this.findInputSlot(\"onTrigger\");\n        if (trigS == -1){ //!trigS || \n            var input = this.addInput(\"onTrigger\", LiteGraph.EVENT, {optional: true, nameLocked: true});\n            return this.findInputSlot(\"onTrigger\");\n        }\n        return trigS;\n    }\n    \n    LGraphNode.prototype.addOnExecutedOutput = function(){\n        var trigS = this.findOutputSlot(\"onExecuted\");\n        if (trigS == -1){ //!trigS || \n            var output = this.addOutput(\"onExecuted\", LiteGraph.ACTION, {optional: true, nameLocked: true});\n            return this.findOutputSlot(\"onExecuted\");\n        }\n        return trigS;\n    }\n    \n    LGraphNode.prototype.onAfterExecuteNode = function(param, options){\n        var trigS = this.findOutputSlot(\"onExecuted\");\n        if (trigS != -1){\n            \n            //console.debug(this.id+\":\"+this.order+\" triggering slot onAfterExecute\");\n            //console.debug(param);\n            //console.debug(options);\n            this.triggerSlot(trigS, param, null, options);\n            \n        }\n    }    \n    \n    LGraphNode.prototype.changeMode = function(modeTo){\n        switch(modeTo){\n            case LiteGraph.ON_EVENT:\n                // this.addOnExecutedOutput();\n                break;\n                \n            case LiteGraph.ON_TRIGGER:\n                this.addOnTriggerInput();\n                this.addOnExecutedOutput();\n                break;\n                \n            case LiteGraph.NEVER:\n                break;\n                \n            case LiteGraph.ALWAYS:\n                break;\n                \n            case LiteGraph.ON_REQUEST:\n                break;\n            \n            default:\n                return false;\n                break;\n        }\n        this.mode = modeTo;\n        return true;\n    };\n\n    /**\n     * Triggers the execution of actions that were deferred when the action was triggered\n     * @method executePendingActions\n     */    \n    LGraphNode.prototype.executePendingActions = function() {\n        if(!this._waiting_actions || !this._waiting_actions.length)\n            return;\n        for(var i = 0; i < this._waiting_actions.length;++i)\n        {\n            var p = this._waiting_actions[i];\n            this.onAction(p[0],p[1],p[2],p[3],p[4]);\n        }        \n        this._waiting_actions.length = 0;\n    }\n\n    \n    /**\n     * Triggers the node code execution, place a boolean/counter to mark the node as being executed\n     * @method doExecute\n     * @param {*} param\n     * @param {*} options\n     */\n    LGraphNode.prototype.doExecute = function(param, options) {\n        options = options || {};\n        if (this.onExecute){\n            \n            // enable this to give the event an ID\n\t\t\tif (!options.action_call) options.action_call = this.id+\"_exec_\"+Math.floor(Math.random()*9999);\n            \n            this.graph.nodes_executing[this.id] = true; //.push(this.id);\n\n            this.onExecute(param, options);\n            \n            this.graph.nodes_executing[this.id] = false; //.pop();\n            \n            // save execution/action ref\n            this.exec_version = this.graph.iteration;\n            if(options && options.action_call){\n                this.action_call = options.action_call; // if (param)\n                this.graph.nodes_executedAction[this.id] = options.action_call;\n            }\n        }\n        else {\n        }\n        this.execute_triggered = 2; // the nFrames it will be used (-- each step), means \"how old\" is the event\n        if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback\n    };\n    \n    /**\n     * Triggers an action, wrapped by logics to control execution flow\n     * @method actionDo\n     * @param {String} action name\n     * @param {*} param\n     */\n    LGraphNode.prototype.actionDo = function(action, param, options, action_slot ) {\n        options = options || {};\n        if (this.onAction){\n            \n\t\t\t// enable this to give the event an ID\n            if (!options.action_call) options.action_call = this.id+\"_\"+(action?action:\"action\")+\"_\"+Math.floor(Math.random()*9999);\n            \n            this.graph.nodes_actioning[this.id] = (action?action:\"actioning\"); //.push(this.id);\n            \n            this.onAction(action, param, options, action_slot);\n            \n            this.graph.nodes_actioning[this.id] = false; //.pop();\n            \n            // save execution/action ref\n            if(options && options.action_call){\n                this.action_call = options.action_call; // if (param)\n                this.graph.nodes_executedAction[this.id] = options.action_call;\n            }\n        }\n        this.action_triggered = 2; // the nFrames it will be used (-- each step), means \"how old\" is the event\n        if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options);\n    };\n    \n    /**\n     * Triggers an event in this node, this will trigger any output with the same name\n     * @method trigger\n     * @param {String} event name ( \"on_play\", ... ) if action is equivalent to false then the event is send to all\n     * @param {*} param\n     */\n    LGraphNode.prototype.trigger = function(action, param, options) {\n        if (!this.outputs || !this.outputs.length) {\n            return;\n        }\n\n        if (this.graph)\n            this.graph._last_trigger_time = LiteGraph.getTime();\n\n        for (var i = 0; i < this.outputs.length; ++i) {\n            var output = this.outputs[i];\n            if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) )\n                continue;\n            this.triggerSlot(i, param, null, options);\n        }\n    };\n\n    /**\n     * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes\n     * @method triggerSlot\n     * @param {Number} slot the index of the output slot\n     * @param {*} param\n     * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot\n     */\n    LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) {\n        options = options || {};\n        if (!this.outputs) {\n            return;\n        }\n\n\t\tif(slot == null)\n\t\t{\n\t\t\tconsole.error(\"slot must be a number\");\n\t\t\treturn;\n\t\t}\n\n\t\tif(slot.constructor !== Number)\n\t\t\tconsole.warn(\"slot must be a number, use node.trigger('name') if you want to use a string\");\n\n        var output = this.outputs[slot];\n        if (!output) {\n            return;\n        }\n\n        var links = output.links;\n        if (!links || !links.length) {\n            return;\n        }\n\n        if (this.graph) {\n            this.graph._last_trigger_time = LiteGraph.getTime();\n        }\n\n        //for every link attached here\n        for (var k = 0; k < links.length; ++k) {\n            var id = links[k];\n            if (link_id != null && link_id != id) {\n                //to skip links\n                continue;\n            }\n            var link_info = this.graph.links[links[k]];\n            if (!link_info) {\n                //not connected\n                continue;\n            }\n            link_info._last_time = LiteGraph.getTime();\n            var node = this.graph.getNodeById(link_info.target_id);\n            if (!node) {\n                //node not found?\n                continue;\n            }\n\n            //used to mark events in graph\n            var target_connection = node.inputs[link_info.target_slot];\n\n\t\t\tif (node.mode === LiteGraph.ON_TRIGGER)\n\t\t\t{\n\t\t\t\t// generate unique trigger ID if not present\n\t\t\t\tif (!options.action_call) options.action_call = this.id+\"_trigg_\"+Math.floor(Math.random()*9999);\n                if (node.onExecute) {\n                    // -- wrapping node.onExecute(param); --\n                    node.doExecute(param, options);\n                }\n\t\t\t}\n\t\t\telse if (node.onAction) {\n                // generate unique action ID if not present\n\t\t\t\tif (!options.action_call) options.action_call = this.id+\"_act_\"+Math.floor(Math.random()*9999);\n                //pass the action name\n                var target_connection = node.inputs[link_info.target_slot];\n\n                //instead of executing them now, it will be executed in the next graph loop, to ensure data flow\n                if(LiteGraph.use_deferred_actions && node.onExecute)\n                {\n                    if(!node._waiting_actions)\n                        node._waiting_actions = [];\n                    node._waiting_actions.push([target_connection.name, param, options, link_info.target_slot]);\n                }\n                else\n                {\n                    // wrap node.onAction(target_connection.name, param);\n                    node.actionDo( target_connection.name, param, options, link_info.target_slot );\n                }\n            }\n        }\n    };\n\n    /**\n     * clears the trigger slot animation\n     * @method clearTriggeredSlot\n     * @param {Number} slot the index of the output slot\n     * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot\n     */\n    LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) {\n        if (!this.outputs) {\n            return;\n        }\n\n        var output = this.outputs[slot];\n        if (!output) {\n            return;\n        }\n\n        var links = output.links;\n        if (!links || !links.length) {\n            return;\n        }\n\n        //for every link attached here\n        for (var k = 0; k < links.length; ++k) {\n            var id = links[k];\n            if (link_id != null && link_id != id) {\n                //to skip links\n                continue;\n            }\n            var link_info = this.graph.links[links[k]];\n            if (!link_info) {\n                //not connected\n                continue;\n            }\n            link_info._last_time = 0;\n        }\n    };\n\n    /**\n     * changes node size and triggers callback\n     * @method setSize\n     * @param {vec2} size\n     */\n    LGraphNode.prototype.setSize = function(size)\n\t{\n\t\tthis.size = size;\n\t\tif(this.onResize)\n\t\t\tthis.onResize(this.size);\n\t}\n\n    /**\n     * add a new property to this node\n     * @method addProperty\n     * @param {string} name\n     * @param {*} default_value\n     * @param {string} type string defining the output type (\"vec3\",\"number\",...)\n     * @param {Object} extra_info this can be used to have special properties of the property (like values, etc)\n     */\n    LGraphNode.prototype.addProperty = function(\n        name,\n        default_value,\n        type,\n        extra_info\n    ) {\n        var o = { name: name, type: type, default_value: default_value };\n        if (extra_info) {\n            for (var i in extra_info) {\n                o[i] = extra_info[i];\n            }\n        }\n        if (!this.properties_info) {\n            this.properties_info = [];\n        }\n        this.properties_info.push(o);\n        if (!this.properties) {\n            this.properties = {};\n        }\n        this.properties[name] = default_value;\n        return o;\n    };\n\n    //connections\n\n    /**\n     * add a new output slot to use in this node\n     * @method addOutput\n     * @param {string} name\n     * @param {string} type string defining the output type (\"vec3\",\"number\",...)\n     * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc)\n     */\n    LGraphNode.prototype.addOutput = function(name, type, extra_info) {\n        var output = { name: name, type: type, links: null };\n        if (extra_info) {\n            for (var i in extra_info) {\n                output[i] = extra_info[i];\n            }\n        }\n\n        if (!this.outputs) {\n            this.outputs = [];\n        }\n        this.outputs.push(output);\n        if (this.onOutputAdded) {\n            this.onOutputAdded(output);\n        }\n        \n        if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true);\n        \n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n        return output;\n    };\n\n    /**\n     * add a new output slot to use in this node\n     * @method addOutputs\n     * @param {Array} array of triplets like [[name,type,extra_info],[...]]\n     */\n    LGraphNode.prototype.addOutputs = function(array) {\n        for (var i = 0; i < array.length; ++i) {\n            var info = array[i];\n            var o = { name: info[0], type: info[1], link: null };\n            if (array[2]) {\n                for (var j in info[2]) {\n                    o[j] = info[2][j];\n                }\n            }\n\n            if (!this.outputs) {\n                this.outputs = [];\n            }\n            this.outputs.push(o);\n            if (this.onOutputAdded) {\n                this.onOutputAdded(o);\n            }\n            \n            if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true);\n            \n        }\n\n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * remove an existing output slot\n     * @method removeOutput\n     * @param {number} slot\n     */\n    LGraphNode.prototype.removeOutput = function(slot) {\n        this.disconnectOutput(slot);\n        this.outputs.splice(slot, 1);\n        for (var i = slot; i < this.outputs.length; ++i) {\n            if (!this.outputs[i] || !this.outputs[i].links) {\n                continue;\n            }\n            var links = this.outputs[i].links;\n            for (var j = 0; j < links.length; ++j) {\n                var link = this.graph.links[links[j]];\n                if (!link) {\n                    continue;\n                }\n                link.origin_slot -= 1;\n            }\n        }\n\n        this.setSize( this.computeSize() );\n        if (this.onOutputRemoved) {\n            this.onOutputRemoved(slot);\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * add a new input slot to use in this node\n     * @method addInput\n     * @param {string} name\n     * @param {string} type string defining the input type (\"vec3\",\"number\",...), it its a generic one use 0\n     * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc)\n     */\n    LGraphNode.prototype.addInput = function(name, type, extra_info) {\n        type = type || 0;\n        var input = { name: name, type: type, link: null };\n        if (extra_info) {\n            for (var i in extra_info) {\n                input[i] = extra_info[i];\n            }\n        }\n\n        if (!this.inputs) {\n            this.inputs = [];\n        }\n\n        this.inputs.push(input);\n        this.setSize( this.computeSize() );\n\n        if (this.onInputAdded) {\n            this.onInputAdded(input);\n\t\t}\n        \n        LiteGraph.registerNodeAndSlotType(this,type);\n\n        this.setDirtyCanvas(true, true);\n        return input;\n    };\n\n    /**\n     * add several new input slots in this node\n     * @method addInputs\n     * @param {Array} array of triplets like [[name,type,extra_info],[...]]\n     */\n    LGraphNode.prototype.addInputs = function(array) {\n        for (var i = 0; i < array.length; ++i) {\n            var info = array[i];\n            var o = { name: info[0], type: info[1], link: null };\n            if (array[2]) {\n                for (var j in info[2]) {\n                    o[j] = info[2][j];\n                }\n            }\n\n            if (!this.inputs) {\n                this.inputs = [];\n            }\n            this.inputs.push(o);\n            if (this.onInputAdded) {\n                this.onInputAdded(o);\n            }\n            \n            LiteGraph.registerNodeAndSlotType(this,info[1]);\n        }\n\n        this.setSize( this.computeSize() );\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * remove an existing input slot\n     * @method removeInput\n     * @param {number} slot\n     */\n    LGraphNode.prototype.removeInput = function(slot) {\n        this.disconnectInput(slot);\n        var slot_info = this.inputs.splice(slot, 1);\n        for (var i = slot; i < this.inputs.length; ++i) {\n            if (!this.inputs[i]) {\n                continue;\n            }\n            var link = this.graph.links[this.inputs[i].link];\n            if (!link) {\n                continue;\n            }\n            link.target_slot -= 1;\n        }\n        this.setSize( this.computeSize() );\n        if (this.onInputRemoved) {\n            this.onInputRemoved(slot, slot_info[0] );\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * add an special connection to this node (used for special kinds of graphs)\n     * @method addConnection\n     * @param {string} name\n     * @param {string} type string defining the input type (\"vec3\",\"number\",...)\n     * @param {[x,y]} pos position of the connection inside the node\n     * @param {string} direction if is input or output\n     */\n    LGraphNode.prototype.addConnection = function(name, type, pos, direction) {\n        var o = {\n            name: name,\n            type: type,\n            pos: pos,\n            direction: direction,\n            links: null\n        };\n        this.connections.push(o);\n        return o;\n    };\n\n    /**\n     * computes the minimum size of a node according to its inputs and output slots\n     * @method computeSize\n     * @param {vec2} minHeight\n     * @return {vec2} the total size\n     */\n    LGraphNode.prototype.computeSize = function(out) {\n        if (this.constructor.size) {\n            return this.constructor.size.concat();\n        }\n\n        var rows = Math.max(\n            this.inputs ? this.inputs.length : 1,\n            this.outputs ? this.outputs.length : 1\n        );\n        var size = out || new Float32Array([0, 0]);\n        rows = Math.max(rows, 1);\n        var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size\n\n        var title_width = compute_text_size(this.title);\n        var input_width = 0;\n        var output_width = 0;\n\n        if (this.inputs) {\n            for (var i = 0, l = this.inputs.length; i < l; ++i) {\n                var input = this.inputs[i];\n                var text = input.label || input.name || \"\";\n                var text_width = compute_text_size(text);\n                if (input_width < text_width) {\n                    input_width = text_width;\n                }\n            }\n        }\n\n        if (this.outputs) {\n            for (var i = 0, l = this.outputs.length; i < l; ++i) {\n                var output = this.outputs[i];\n                var text = output.label || output.name || \"\";\n                var text_width = compute_text_size(text);\n                if (output_width < text_width) {\n                    output_width = text_width;\n                }\n            }\n        }\n\n        size[0] = Math.max(input_width + output_width + 10, title_width);\n        size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH);\n        if (this.widgets && this.widgets.length) {\n            size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5);\n        }\n\n        size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT;\n\n        var widgets_height = 0;\n        if (this.widgets && this.widgets.length) {\n            for (var i = 0, l = this.widgets.length; i < l; ++i) {\n                if (this.widgets[i].computeSize)\n                    widgets_height += this.widgets[i].computeSize(size[0])[1] + 4;\n                else\n                    widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4;\n            }\n            widgets_height += 8;\n        }\n\n        //compute height using widgets height\n        if( this.widgets_up )\n            size[1] = Math.max( size[1], widgets_height );\n        else if( this.widgets_start_y != null )\n            size[1] = Math.max( size[1], widgets_height + this.widgets_start_y );\n        else\n            size[1] += widgets_height;\n\n        function compute_text_size(text) {\n            if (!text) {\n                return 0;\n            }\n            return font_size * text.length * 0.6;\n        }\n\n        if (\n            this.constructor.min_height &&\n            size[1] < this.constructor.min_height\n        ) {\n            size[1] = this.constructor.min_height;\n        }\n\n        size[1] += 6; //margin\n\n        return size;\n    };\n\n    /**\n     * returns all the info available about a property of this node.\n     *\n     * @method getPropertyInfo\n     * @param {String} property name of the property\n     * @return {Object} the object with all the available info\n    */\n    LGraphNode.prototype.getPropertyInfo = function( property )\n\t{\n        var info = null;\n\n\t\t//there are several ways to define info about a property\n\t\t//legacy mode\n\t\tif (this.properties_info) {\n            for (var i = 0; i < this.properties_info.length; ++i) {\n                if (this.properties_info[i].name == property) {\n                    info = this.properties_info[i];\n                    break;\n                }\n            }\n        }\n\t\t//litescene mode using the constructor\n\t\tif(this.constructor[\"@\" + property])\n\t\t\tinfo = this.constructor[\"@\" + property];\n\n\t\tif(this.constructor.widgets_info && this.constructor.widgets_info[property])\n\t\t\tinfo = this.constructor.widgets_info[property];\n\n\t\t//litescene mode using the constructor\n\t\tif (!info && this.onGetPropertyInfo) {\n            info = this.onGetPropertyInfo(property);\n        }\n\n        if (!info)\n            info = {};\n\t\tif(!info.type)\n\t\t\tinfo.type = typeof this.properties[property];\n\t\tif(info.widget == \"combo\")\n\t\t\tinfo.type = \"enum\";\n\n\t\treturn info;\n\t}\n\n    /**\n     * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties\n     *\n     * @method addWidget\n     * @param {String} type the widget type (could be \"number\",\"string\",\"combo\"\n     * @param {String} name the text to show on the widget\n     * @param {String} value the default value\n     * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify)\n     * @param {Object} options the object that contains special properties of this widget \n     * @return {Object} the created widget object\n     */\n    LGraphNode.prototype.addWidget = function( type, name, value, callback, options )\n\t{\n        if (!this.widgets) {\n            this.widgets = [];\n        }\n\n\t\tif(!options && callback && callback.constructor === Object)\n\t\t{\n\t\t\toptions = callback;\n\t\t\tcallback = null;\n\t\t}\n\n\t\tif(options && options.constructor === String) //options can be the property name\n\t\t\toptions = { property: options };\n\n\t\tif(callback && callback.constructor === String) //callback can be the property name\n\t\t{\n\t\t\tif(!options)\n\t\t\t\toptions = {};\n\t\t\toptions.property = callback;\n\t\t\tcallback = null;\n\t\t}\n\n\t\tif(callback && callback.constructor !== Function)\n\t\t{\n\t\t\tconsole.warn(\"addWidget: callback must be a function\");\n\t\t\tcallback = null;\n\t\t}\n\n        var w = {\n            type: type.toLowerCase(),\n            name: name,\n            value: value,\n            callback: callback,\n            options: options || {}\n        };\n\n        if (w.options.y !== undefined) {\n            w.y = w.options.y;\n        }\n\n        if (!callback && !w.options.callback && !w.options.property) {\n            console.warn(\"LiteGraph addWidget(...) without a callback or property assigned\");\n        }\n        if (type == \"combo\" && !w.options.values) {\n            throw \"LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }\";\n        }\n        this.widgets.push(w);\n\t\tthis.setSize( this.computeSize() );\n        return w;\n    };\n\n    LGraphNode.prototype.addCustomWidget = function(custom_widget) {\n        if (!this.widgets) {\n            this.widgets = [];\n        }\n        this.widgets.push(custom_widget);\n        return custom_widget;\n    };\n\n    /**\n     * returns the bounding of the object, used for rendering purposes\n     * @method getBounding\n     * @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage\n     * @param compute_outer {boolean?} [optional] set to true to include the shadow and connection points in the bounding calculation\n     * @return {Float32Array[4]} the bounding box in format of [topleft_cornerx, topleft_cornery, width, height]\n     */\n    LGraphNode.prototype.getBounding = function(out, compute_outer) {\n        out = out || new Float32Array(4);\n        const nodePos = this.pos;\n        const isCollapsed = this.flags.collapsed;\n        const nodeSize = this.size;\n        \n        let left_offset = 0;\n        // 1 offset due to how nodes are rendered\n        let right_offset =  1 ;\n        let top_offset = 0;\n        let bottom_offset = 0;\n        \n        if (compute_outer) {\n            // 4 offset for collapsed node connection points\n            left_offset = 4;\n            // 6 offset for right shadow and collapsed node connection points\n            right_offset = 6 + left_offset;\n            // 4 offset for collapsed nodes top connection points\n            top_offset = 4;\n            // 5 offset for bottom shadow and collapsed node connection points\n            bottom_offset = 5 + top_offset;\n        }\n        \n        out[0] = nodePos[0] - left_offset;\n        out[1] = nodePos[1] - LiteGraph.NODE_TITLE_HEIGHT - top_offset;\n        out[2] = isCollapsed ?\n            (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + right_offset :\n            nodeSize[0] + right_offset;\n        out[3] = isCollapsed ?\n            LiteGraph.NODE_TITLE_HEIGHT + bottom_offset :\n            nodeSize[1] + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset;\n\n        if (this.onBounding) {\n            this.onBounding(out);\n        }\n        return out;\n    };\n\n    /**\n     * checks if a point is inside the shape of a node\n     * @method isPointInside\n     * @param {number} x\n     * @param {number} y\n     * @return {boolean}\n     */\n    LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) {\n        margin = margin || 0;\n\n        var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT;\n        if (skip_title) {\n            margin_top = 0;\n        }\n        if (this.flags && this.flags.collapsed) {\n            //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS)\n            if (\n                isInsideRectangle(\n                    x,\n                    y,\n                    this.pos[0] - margin,\n                    this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin,\n                    (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) +\n                        2 * margin,\n                    LiteGraph.NODE_TITLE_HEIGHT + 2 * margin\n                )\n            ) {\n                return true;\n            }\n        } else if (\n            this.pos[0] - 4 - margin < x &&\n            this.pos[0] + this.size[0] + 4 + margin > x &&\n            this.pos[1] - margin_top - margin < y &&\n            this.pos[1] + this.size[1] + margin > y\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    /**\n     * checks if a point is inside a node slot, and returns info about which slot\n     * @method getSlotInPosition\n     * @param {number} x\n     * @param {number} y\n     * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }\n     */\n    LGraphNode.prototype.getSlotInPosition = function(x, y) {\n        //search for inputs\n        var link_pos = new Float32Array(2);\n        if (this.inputs) {\n            for (var i = 0, l = this.inputs.length; i < l; ++i) {\n                var input = this.inputs[i];\n                this.getConnectionPos(true, i, link_pos);\n                if (\n                    isInsideRectangle(\n                        x,\n                        y,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        20,\n                        10\n                    )\n                ) {\n                    return { input: input, slot: i, link_pos: link_pos };\n                }\n            }\n        }\n\n        if (this.outputs) {\n            for (var i = 0, l = this.outputs.length; i < l; ++i) {\n                var output = this.outputs[i];\n                this.getConnectionPos(false, i, link_pos);\n                if (\n                    isInsideRectangle(\n                        x,\n                        y,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        20,\n                        10\n                    )\n                ) {\n                    return { output: output, slot: i, link_pos: link_pos };\n                }\n            }\n        }\n\n        return null;\n    };\n\n    /**\n     * returns the input slot with a given name (used for dynamic slots), -1 if not found\n     * @method findInputSlot\n     * @param {string} name the name of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findInputSlot = function(name,  returnObj) {\n        if (!this.inputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            if (name == this.inputs[i].name) {\n                return !returnObj ? i : this.inputs[i];\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * returns the output slot with a given name (used for dynamic slots), -1 if not found\n     * @method findOutputSlot\n     * @param {string} name the name of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findOutputSlot = function(name, returnObj) {\n        returnObj = returnObj || false;\n        if (!this.outputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            if (name == this.outputs[i].name) {\n                return !returnObj ? i : this.outputs[i];\n            }\n        }\n        return -1;\n    };\n    \n    // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options\n    \n    /**\n     * returns the first free input slot\n     * @method findInputSlotFree\n     * @param {object} options\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findInputSlotFree = function(optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = {returnObj: false\n                        ,typesNotAccepted: []\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (!this.inputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.inputs.length; i < l; ++i) {\n            if (this.inputs[i].link && this.inputs[i].link != null) {\n                continue;\n            }\n            if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){\n                continue;\n            }\n            return !opts.returnObj ? i : this.inputs[i];\n        }\n        return -1;\n    };\n\n    /**\n     * returns the first output slot free\n     * @method findOutputSlotFree\n     * @param {object} options\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findOutputSlotFree = function(optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { returnObj: false\n                        ,typesNotAccepted: []\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (!this.outputs) {\n            return -1;\n        }\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            if (this.outputs[i].links && this.outputs[i].links != null) {\n                continue;\n            }\n            if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){\n                continue;\n            }\n            return !opts.returnObj ? i : this.outputs[i];\n        }\n        return -1;\n    };\n    \n    /**\n     * findSlotByType for INPUTS\n     */\n    LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied);\n    };\n\n    /**\n     * findSlotByType for OUTPUTS\n     */\n    LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied);\n    };\n    \n    /**\n     * returns the output (or input) slot with a given type, -1 if not found\n     * @method findSlotByType\n     * @param {boolean} input uise inputs instead of outputs\n     * @param {string} type the type of the slot\n     * @param {boolean} returnObj if the obj itself wanted\n     * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway)\n     * @return {number_or_object} the slot (-1 if not found)\n     */\n    LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) {\n        input = input || false;\n        returnObj = returnObj || false;\n        preferFreeSlot = preferFreeSlot || false;\n        doNotUseOccupied = doNotUseOccupied || false;\n        var aSlots = input ? this.inputs : this.outputs;\n        if (!aSlots) {\n            return -1;\n        }\n\t\t// !! empty string type is considered 0, * !!\n\t\tif (type == \"\" || type == \"*\") type = 0; \n        for (var i = 0, l = aSlots.length; i < l; ++i) {\n            var tFound = false;\n            var aSource = (type+\"\").toLowerCase().split(\",\");\n            var aDest = aSlots[i].type==\"0\"||aSlots[i].type==\"*\"?\"0\":aSlots[i].type;\n\t\t\taDest = (aDest+\"\").toLowerCase().split(\",\");\n            for(var sI=0;sI<aSource.length;sI++){\n                for(var dI=0;dI<aDest.length;dI++){\n\t\t\t\t\tif (aSource[sI]==\"_event_\") aSource[sI] = LiteGraph.EVENT;\n\t\t\t\t\tif (aDest[sI]==\"_event_\") aDest[sI] = LiteGraph.EVENT;\n\t\t\t\t\tif (aSource[sI]==\"*\") aSource[sI] = 0;\n\t\t\t\t\tif (aDest[sI]==\"*\") aDest[sI] = 0;\n\t\t\t\t\tif (aSource[sI] == aDest[dI]) {\n                        if (preferFreeSlot && aSlots[i].links && aSlots[i].links !== null) continue;\n                        return !returnObj ? i : aSlots[i];\n                    }\n                }\n            }\n        }\n        // if didnt find some, stop checking for free slots\n        if (preferFreeSlot && !doNotUseOccupied){\n            for (var i = 0, l = aSlots.length; i < l; ++i) {\n                var tFound = false;\n                var aSource = (type+\"\").toLowerCase().split(\",\");\n                var aDest = aSlots[i].type==\"0\"||aSlots[i].type==\"*\"?\"0\":aSlots[i].type;\n\t\t\t\taDest = (aDest+\"\").toLowerCase().split(\",\");\n                for(var sI=0;sI<aSource.length;sI++){\n                    for(var dI=0;dI<aDest.length;dI++){\n\t\t\t\t\t\tif (aSource[sI]==\"*\") aSource[sI] = 0;\n\t\t\t\t\t\tif (aDest[sI]==\"*\") aDest[sI] = 0;\n                        if (aSource[sI] == aDest[dI]) {\n                            return !returnObj ? i : aSlots[i];\n                        }\n                    }\n                }\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * connect this node output to the input of another node BY TYPE\n     * @method connectByType\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {string} target_type the input slot type of the target node\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connectByType = function(slot, target_node, target_slotType, optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { createEventInCase: true\n\t\t\t\t\t   \t,firstFreeIfOutputGeneralInCase: true\n                        ,generalTypeInCase: true\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (target_node && target_node.constructor === Number) {\n            target_node = this.graph.getNodeById(target_node);\n        }\n        var target_slot = target_node.findInputSlotByType(target_slotType, false, true);\n        if (target_slot >= 0 && target_slot !== null){\n            //console.debug(\"CONNbyTYPE type \"+target_slotType+\" for \"+target_slot)\n            return this.connect(slot, target_node, target_slot);\n        }else{\n            //console.log(\"type \"+target_slotType+\" not found or not free?\")\n            if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){\n                // WILL CREATE THE onTrigger IN SLOT\n\t\t\t\t//console.debug(\"connect WILL CREATE THE onTrigger \"+target_slotType+\" to \"+target_node);\n                return this.connect(slot, target_node, -1);\n            }\n\t\t\t// connect to the first general output slot if not found a specific type and \n            if (opts.generalTypeInCase){\n                var target_slot = target_node.findInputSlotByType(0, false, true, true);\n\t\t\t\t//console.debug(\"connect TO a general type (*, 0), if not found the specific type \",target_slotType,\" to \",target_node,\"RES_SLOT:\",target_slot);\n                if (target_slot >= 0){\n                    return this.connect(slot, target_node, target_slot);\n                }\n            }\n            // connect to the first free input slot if not found a specific type and this output is general\n            if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == \"*\" || target_slotType == \"\")){\n                var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] });\n\t\t\t\t//console.debug(\"connect TO TheFirstFREE \",target_slotType,\" to \",target_node,\"RES_SLOT:\",target_slot);\n                if (target_slot >= 0){\n\t\t\t\t\treturn this.connect(slot, target_node, target_slot);\n                }\n            }\n\t\t\t\n\t\t\tconsole.debug(\"no way to connect type: \",target_slotType,\" to targetNODE \",target_node);\n\t\t\t//TODO filter\n\t\t\t\n            return null;\n        }\n    }\n    \n    /**\n     * connect this node input to the output of another node BY TYPE\n     * @method connectByType\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {string} target_type the output slot type of the target node\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) {\n        var optsIn = optsIn || {};\n        var optsDef = { createEventInCase: true\n                        ,firstFreeIfInputGeneralInCase: true\n                        ,generalTypeInCase: true\n                      };\n        var opts = Object.assign(optsDef,optsIn);\n        if (source_node && source_node.constructor === Number) {\n            source_node = this.graph.getNodeById(source_node);\n        }\n        var source_slot = source_node.findOutputSlotByType(source_slotType, false, true);\n        if (source_slot >= 0 && source_slot !== null){\n            //console.debug(\"CONNbyTYPE OUT! type \"+source_slotType+\" for \"+source_slot)\n            return source_node.connect(source_slot, this, slot);\n        }else{\n            \n            // connect to the first general output slot if not found a specific type and \n            if (opts.generalTypeInCase){\n                var source_slot = source_node.findOutputSlotByType(0, false, true, true);\n                if (source_slot >= 0){\n                    return source_node.connect(source_slot, this, slot);\n                }\n            }\n            \n            if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){\n                // WILL CREATE THE onExecuted OUT SLOT\n\t\t\t\tif (LiteGraph.do_add_triggers_slots){\n\t\t\t\t\tvar source_slot = source_node.addOnExecutedOutput();\n\t\t\t\t\treturn source_node.connect(source_slot, this, slot);\n\t\t\t\t}\n            }\n            // connect to the first free output slot if not found a specific type and this input is general\n            if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == \"*\" || source_slotType == \"\")){\n                var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] });\n                if (source_slot >= 0){\n                    return source_node.connect(source_slot, this, slot);\n                }\n            }\n            \n\t\t\tconsole.debug(\"no way to connect byOUT type: \",source_slotType,\" to sourceNODE \",source_node);\n\t\t\t//TODO filter\n\t\t\t\n            //console.log(\"type OUT! \"+source_slotType+\" not found or not free?\")\n            return null;\n        }\n    }\n    \n    /**\n     * connect this node output to the input of another node\n     * @method connect\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} node the target node\n     * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)\n     * @return {Object} the link_info is created, otherwise null\n     */\n    LGraphNode.prototype.connect = function(slot, target_node, target_slot) {\n        target_slot = target_slot || 0;\n\n        if (!this.graph) {\n            //could be connected before adding it to a graph\n            console.log(\n                \"Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them.\"\n            ); //due to link ids being associated with graphs\n            return null;\n        }\n\n        //seek for the output slot\n        if (slot.constructor === String) {\n            slot = this.findOutputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return null;\n            }\n        } else if (!this.outputs || slot >= this.outputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return null;\n        }\n\n        if (target_node && target_node.constructor === Number) {\n            target_node = this.graph.getNodeById(target_node);\n        }\n        if (!target_node) {\n            throw \"target node is null\";\n        }\n\n        //avoid loopback\n        if (target_node == this) {\n            return null;\n        }\n\n        //you can specify the slot by name\n        if (target_slot.constructor === String) {\n            target_slot = target_node.findInputSlot(target_slot);\n            if (target_slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\n                        \"Connect: Error, no slot of name \" + target_slot\n                    );\n                }\n                return null;\n            }\n        } else if (target_slot === LiteGraph.EVENT) {\n            \n            if (LiteGraph.do_add_triggers_slots){\n\t            //search for first slot with event? :: NO this is done outside\n\t\t\t\t//console.log(\"Connect: Creating triggerEvent\");\n\t            // force mode\n\t            target_node.changeMode(LiteGraph.ON_TRIGGER);\n\t            target_slot = target_node.findInputSlot(\"onTrigger\");\n        \t}else{\n            \treturn null; // -- break --\n\t\t\t}\n        } else if (\n            !target_node.inputs ||\n            target_slot >= target_node.inputs.length\n        ) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return null;\n        }\n\n\t\tvar changed = false;\n\n        var input = target_node.inputs[target_slot];\n        var link_info = null;\n        var output = this.outputs[slot];\n        \n        if (!this.outputs[slot]){\n            /*console.debug(\"Invalid slot passed: \"+slot);\n            console.debug(this.outputs);*/\n            return null;\n        }\n\n        // allow target node to change slot\n        if (target_node.onBeforeConnectInput) {\n            // This way node can choose another slot (or make a new one?)\n            target_slot = target_node.onBeforeConnectInput(target_slot); //callback\n        }\n\n\t\t//check target_slot and check connection types\n        if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type))\n\t\t{\n\t        this.setDirtyCanvas(false, true);\n\t\t\tif(changed)\n\t\t        this.graph.connectionChange(this, link_info);\n\t\t\treturn null;\n\t\t}else{\n\t\t\t//console.debug(\"valid connection\",output.type, input.type);\n\t\t}\n\n        //allows nodes to block connection, callback\n        if (target_node.onConnectInput) {\n            if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) {\n                return null;\n            }\n        }\n        if (this.onConnectOutput) { // callback\n            if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) {\n                return null;\n            }\n        }\n\n        //if there is something already plugged there, disconnect\n        if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) {\n\t\t\tthis.graph.beforeChange();\n            target_node.disconnectInput(target_slot, {doProcessChange: false});\n\t\t\tchanged = true;\n        }\n        if (output.links !== null && output.links.length){\n            switch(output.type){\n                case LiteGraph.EVENT:\n                    if (!LiteGraph.allow_multi_output_for_events){\n                        this.graph.beforeChange();\n                        this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false});\n                        changed = true;\n                    }\n                break;\n                default:\n                break;\n            }\n        }\n\n        var nextId\n        if (LiteGraph.use_uuids)\n            nextId = LiteGraph.uuidv4();\n        else\n            nextId = ++this.graph.last_link_id;\n        \n\t\t//create link class\n\t\tlink_info = new LLink(\n\t\t\tnextId,\n\t\t\tinput.type || output.type,\n\t\t\tthis.id,\n\t\t\tslot,\n\t\t\ttarget_node.id,\n\t\t\ttarget_slot\n\t\t);\n\n\t\t//add to graph links list\n\t\tthis.graph.links[link_info.id] = link_info;\n\n\t\t//connect in output\n\t\tif (output.links == null) {\n\t\t\toutput.links = [];\n\t\t}\n\t\toutput.links.push(link_info.id);\n\t\t//connect in input\n\t\ttarget_node.inputs[target_slot].link = link_info.id;\n\t\tif (this.graph) {\n\t\t\tthis.graph._version++;\n\t\t}\n\t\tif (this.onConnectionsChange) {\n\t\t\tthis.onConnectionsChange(\n\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\tslot,\n\t\t\t\ttrue,\n\t\t\t\tlink_info,\n\t\t\t\toutput\n\t\t\t);\n\t\t} //link_info has been created now, so its updated\n\t\tif (target_node.onConnectionsChange) {\n\t\t\ttarget_node.onConnectionsChange(\n\t\t\t\tLiteGraph.INPUT,\n\t\t\t\ttarget_slot,\n\t\t\t\ttrue,\n\t\t\t\tlink_info,\n\t\t\t\tinput\n\t\t\t);\n\t\t}\n\t\tif (this.graph && this.graph.onNodeConnectionChange) {\n\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\tLiteGraph.INPUT,\n\t\t\t\ttarget_node,\n\t\t\t\ttarget_slot,\n\t\t\t\tthis,\n\t\t\t\tslot\n\t\t\t);\n\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\tthis,\n\t\t\t\tslot,\n\t\t\t\ttarget_node,\n\t\t\t\ttarget_slot\n\t\t\t);\n\t\t}\n\n        this.setDirtyCanvas(false, true);\n\t\tthis.graph.afterChange();\n\t\tthis.graph.connectionChange(this, link_info);\n\n        return link_info;\n    };\n\n    /**\n     * disconnect one output to an specific node\n     * @method disconnectOutput\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]\n     * @return {boolean} if it was disconnected successfully\n     */\n    LGraphNode.prototype.disconnectOutput = function(slot, target_node) {\n        if (slot.constructor === String) {\n            slot = this.findOutputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return false;\n            }\n        } else if (!this.outputs || slot >= this.outputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return false;\n        }\n\n        //get output slot\n        var output = this.outputs[slot];\n        if (!output || !output.links || output.links.length == 0) {\n            return false;\n        }\n\n        //one of the output links in this slot\n        if (target_node) {\n            if (target_node.constructor === Number) {\n                target_node = this.graph.getNodeById(target_node);\n            }\n            if (!target_node) {\n                throw \"Target Node not found\";\n            }\n\n            for (var i = 0, l = output.links.length; i < l; i++) {\n                var link_id = output.links[i];\n                var link_info = this.graph.links[link_id];\n\n                //is the link we are searching for...\n                if (link_info.target_id == target_node.id) {\n                    output.links.splice(i, 1); //remove here\n                    var input = target_node.inputs[link_info.target_slot];\n                    input.link = null; //remove there\n                    delete this.graph.links[link_id]; //remove the link from the links pool\n                    if (this.graph) {\n                        this.graph._version++;\n                    }\n                    if (target_node.onConnectionsChange) {\n                        target_node.onConnectionsChange(\n                            LiteGraph.INPUT,\n                            link_info.target_slot,\n                            false,\n                            link_info,\n                            input\n                        );\n                    } //link_info hasn't been modified so its ok\n                    if (this.onConnectionsChange) {\n                        this.onConnectionsChange(\n                            LiteGraph.OUTPUT,\n                            slot,\n                            false,\n                            link_info,\n                            output\n                        );\n                    }\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.OUTPUT,\n                            this,\n                            slot\n                        );\n                    }\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.OUTPUT,\n                            this,\n                            slot\n                        );\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.INPUT,\n                            target_node,\n                            link_info.target_slot\n                        );\n                    }\n                    break;\n                }\n            }\n        } //all the links in this output slot\n        else {\n            for (var i = 0, l = output.links.length; i < l; i++) {\n                var link_id = output.links[i];\n                var link_info = this.graph.links[link_id];\n                if (!link_info) {\n                    //bug: it happens sometimes\n                    continue;\n                }\n\n                var target_node = this.graph.getNodeById(link_info.target_id);\n                var input = null;\n                if (this.graph) {\n                    this.graph._version++;\n                }\n                if (target_node) {\n                    input = target_node.inputs[link_info.target_slot];\n                    input.link = null; //remove other side link\n                    if (target_node.onConnectionsChange) {\n                        target_node.onConnectionsChange(\n                            LiteGraph.INPUT,\n                            link_info.target_slot,\n                            false,\n                            link_info,\n                            input\n                        );\n                    } //link_info hasn't been modified so its ok\n                    if (this.graph && this.graph.onNodeConnectionChange) {\n                        this.graph.onNodeConnectionChange(\n                            LiteGraph.INPUT,\n                            target_node,\n                            link_info.target_slot\n                        );\n                    }\n                }\n                delete this.graph.links[link_id]; //remove the link from the links pool\n                if (this.onConnectionsChange) {\n                    this.onConnectionsChange(\n                        LiteGraph.OUTPUT,\n                        slot,\n                        false,\n                        link_info,\n                        output\n                    );\n                }\n                if (this.graph && this.graph.onNodeConnectionChange) {\n                    this.graph.onNodeConnectionChange(\n                        LiteGraph.OUTPUT,\n                        this,\n                        slot\n                    );\n                    this.graph.onNodeConnectionChange(\n                        LiteGraph.INPUT,\n                        target_node,\n                        link_info.target_slot\n                    );\n                }\n            }\n            output.links = null;\n        }\n\n        this.setDirtyCanvas(false, true);\n        this.graph.connectionChange(this);\n        return true;\n    };\n\n    /**\n     * disconnect one input\n     * @method disconnectInput\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @return {boolean} if it was disconnected successfully\n     */\n    LGraphNode.prototype.disconnectInput = function(slot) {\n        //seek for the output slot\n        if (slot.constructor === String) {\n            slot = this.findInputSlot(slot);\n            if (slot == -1) {\n                if (LiteGraph.debug) {\n                    console.log(\"Connect: Error, no slot of name \" + slot);\n                }\n                return false;\n            }\n        } else if (!this.inputs || slot >= this.inputs.length) {\n            if (LiteGraph.debug) {\n                console.log(\"Connect: Error, slot number not found\");\n            }\n            return false;\n        }\n\n        var input = this.inputs[slot];\n        if (!input) {\n            return false;\n        }\n\n        var link_id = this.inputs[slot].link;\n\t\tif(link_id != null)\n\t\t{\n\t\t\tthis.inputs[slot].link = null;\n\n\t\t\t//remove other side\n\t\t\tvar link_info = this.graph.links[link_id];\n\t\t\tif (link_info) {\n\t\t\t\tvar target_node = this.graph.getNodeById(link_info.origin_id);\n\t\t\t\tif (!target_node) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tvar output = target_node.outputs[link_info.origin_slot];\n\t\t\t\tif (!output || !output.links || output.links.length == 0) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t//search in the inputs list for this link\n\t\t\t\tfor (var i = 0, l = output.links.length; i < l; i++) {\n\t\t\t\t\tif (output.links[i] == link_id) {\n\t\t\t\t\t\toutput.links.splice(i, 1);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tdelete this.graph.links[link_id]; //remove from the pool\n\t\t\t\tif (this.graph) {\n\t\t\t\t\tthis.graph._version++;\n\t\t\t\t}\n\t\t\t\tif (this.onConnectionsChange) {\n\t\t\t\t\tthis.onConnectionsChange(\n\t\t\t\t\t\tLiteGraph.INPUT,\n\t\t\t\t\t\tslot,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tlink_info,\n\t\t\t\t\t\tinput\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (target_node.onConnectionsChange) {\n\t\t\t\t\ttarget_node.onConnectionsChange(\n\t\t\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\t\t\ti,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tlink_info,\n\t\t\t\t\t\toutput\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (this.graph && this.graph.onNodeConnectionChange) {\n\t\t\t\t\tthis.graph.onNodeConnectionChange(\n\t\t\t\t\t\tLiteGraph.OUTPUT,\n\t\t\t\t\t\ttarget_node,\n\t\t\t\t\t\ti\n\t\t\t\t\t);\n\t\t\t\t\tthis.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot);\n\t\t\t\t}\n\t\t\t}\n\t\t} //link != null\n\n        this.setDirtyCanvas(false, true);\n\t\tif(this.graph)\n\t        this.graph.connectionChange(this);\n        return true;\n    };\n\n    /**\n     * returns the center of a connection point in canvas coords\n     * @method getConnectionPos\n     * @param {boolean} is_input true if if a input slot, false if it is an output\n     * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)\n     * @param {vec2} out [optional] a place to store the output, to free garbage\n     * @return {[x,y]} the position\n     **/\n    LGraphNode.prototype.getConnectionPos = function(\n        is_input,\n        slot_number,\n        out\n    ) {\n        out = out || new Float32Array(2);\n        var num_slots = 0;\n        if (is_input && this.inputs) {\n            num_slots = this.inputs.length;\n        }\n        if (!is_input && this.outputs) {\n            num_slots = this.outputs.length;\n        }\n\n        var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n\n        if (this.flags.collapsed) {\n            var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;\n            if (this.horizontal) {\n                out[0] = this.pos[0] + w * 0.5;\n                if (is_input) {\n                    out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n                } else {\n                    out[1] = this.pos[1];\n                }\n            } else {\n                if (is_input) {\n                    out[0] = this.pos[0];\n                } else {\n                    out[0] = this.pos[0] + w;\n                }\n                out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            }\n            return out;\n        }\n\n        //weird feature that never got finished\n        if (is_input && slot_number == -1) {\n            out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;\n            return out;\n        }\n\n        //hard-coded pos\n        if (\n            is_input &&\n            num_slots > slot_number &&\n            this.inputs[slot_number].pos\n        ) {\n            out[0] = this.pos[0] + this.inputs[slot_number].pos[0];\n            out[1] = this.pos[1] + this.inputs[slot_number].pos[1];\n            return out;\n        } else if (\n            !is_input &&\n            num_slots > slot_number &&\n            this.outputs[slot_number].pos\n        ) {\n            out[0] = this.pos[0] + this.outputs[slot_number].pos[0];\n            out[1] = this.pos[1] + this.outputs[slot_number].pos[1];\n            return out;\n        }\n\n        //horizontal distributed slots\n        if (this.horizontal) {\n            out[0] =\n                this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots);\n            if (is_input) {\n                out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;\n            } else {\n                out[1] = this.pos[1] + this.size[1];\n            }\n            return out;\n        }\n\n        //default vertical slots\n        if (is_input) {\n            out[0] = this.pos[0] + offset;\n        } else {\n            out[0] = this.pos[0] + this.size[0] + 1 - offset;\n        }\n        out[1] =\n            this.pos[1] +\n            (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT +\n            (this.constructor.slot_start_y || 0);\n        return out;\n    };\n\n    /* Force align to grid */\n    LGraphNode.prototype.alignToGrid = function() {\n        this.pos[0] =\n            LiteGraph.CANVAS_GRID_SIZE *\n            Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE);\n        this.pos[1] =\n            LiteGraph.CANVAS_GRID_SIZE *\n            Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE);\n    };\n\n    /* Console output */\n    LGraphNode.prototype.trace = function(msg) {\n        if (!this.console) {\n            this.console = [];\n        }\n\n        this.console.push(msg);\n        if (this.console.length > LGraphNode.MAX_CONSOLE) {\n            this.console.shift();\n        }\n\n\t\tif(this.graph.onNodeTrace)\n\t        this.graph.onNodeTrace(this, msg);\n    };\n\n    /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */\n    LGraphNode.prototype.setDirtyCanvas = function(\n        dirty_foreground,\n        dirty_background\n    ) {\n        if (!this.graph) {\n            return;\n        }\n        this.graph.sendActionToCanvas(\"setDirty\", [\n            dirty_foreground,\n            dirty_background\n        ]);\n    };\n\n    LGraphNode.prototype.loadImage = function(url) {\n        var img = new Image();\n        img.src = LiteGraph.node_images_path + url;\n        img.ready = false;\n\n        var that = this;\n        img.onload = function() {\n            this.ready = true;\n            that.setDirtyCanvas(true);\n        };\n        return img;\n    };\n\n    //safe LGraphNode action execution (not sure if safe)\n    /*\nLGraphNode.prototype.executeAction = function(action)\n{\n\tif(action == \"\") return false;\n\n\tif( action.indexOf(\";\") != -1 || action.indexOf(\"}\") != -1)\n\t{\n\t\tthis.trace(\"Error: Action contains unsafe characters\");\n\t\treturn false;\n\t}\n\n\tvar tokens = action.split(\"(\");\n\tvar func_name = tokens[0];\n\tif( typeof(this[func_name]) != \"function\")\n\t{\n\t\tthis.trace(\"Error: Action not found on node: \" + func_name);\n\t\treturn false;\n\t}\n\n\tvar code = action;\n\n\ttry\n\t{\n\t\tvar _foo = eval;\n\t\teval = null;\n\t\t(new Function(\"with(this) { \" + code + \"}\")).call(this);\n\t\teval = _foo;\n\t}\n\tcatch (err)\n\t{\n\t\tthis.trace(\"Error executing action {\" + action + \"} :\" + err);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n*/\n\n    /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */\n    LGraphNode.prototype.captureInput = function(v) {\n        if (!this.graph || !this.graph.list_of_graphcanvas) {\n            return;\n        }\n\n        var list = this.graph.list_of_graphcanvas;\n\n        for (var i = 0; i < list.length; ++i) {\n            var c = list[i];\n            //releasing somebody elses capture?!\n            if (!v && c.node_capturing_input != this) {\n                continue;\n            }\n\n            //change\n            c.node_capturing_input = v ? this : null;\n        }\n    };\n\n    /**\n     * Collapse the node to make it smaller on the canvas\n     * @method collapse\n     **/\n    LGraphNode.prototype.collapse = function(force) {\n        this.graph._version++;\n        if (this.constructor.collapsable === false && !force) {\n            return;\n        }\n        if (!this.flags.collapsed) {\n            this.flags.collapsed = true;\n        } else {\n            this.flags.collapsed = false;\n        }\n        this.setDirtyCanvas(true, true);\n    };\n\n    /**\n     * Forces the node to do not move or realign on Z\n     * @method pin\n     **/\n\n    LGraphNode.prototype.pin = function(v) {\n        this.graph._version++;\n        if (v === undefined) {\n            this.flags.pinned = !this.flags.pinned;\n        } else {\n            this.flags.pinned = v;\n        }\n    };\n\n    LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) {\n        return [\n            (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0],\n            (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]\n        ];\n    };\n\n    function LGraphGroup(title) {\n        this._ctor(title);\n    }\n\n    global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup;\n\n    LGraphGroup.prototype._ctor = function(title) {\n        this.title = title || \"Group\";\n        this.font_size = 24;\n        this.color = LGraphCanvas.node_colors.pale_blue\n            ? LGraphCanvas.node_colors.pale_blue.groupcolor\n            : \"#AAA\";\n        this._bounding = new Float32Array([10, 10, 140, 80]);\n        this._pos = this._bounding.subarray(0, 2);\n        this._size = this._bounding.subarray(2, 4);\n        this._nodes = [];\n        this.graph = null;\n\n        Object.defineProperty(this, \"pos\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._pos[0] = v[0];\n                this._pos[1] = v[1];\n            },\n            get: function() {\n                return this._pos;\n            },\n            enumerable: true\n        });\n\n        Object.defineProperty(this, \"size\", {\n            set: function(v) {\n                if (!v || v.length < 2) {\n                    return;\n                }\n                this._size[0] = Math.max(140, v[0]);\n                this._size[1] = Math.max(80, v[1]);\n            },\n            get: function() {\n                return this._size;\n            },\n            enumerable: true\n        });\n    };\n\n    LGraphGroup.prototype.configure = function(o) {\n        this.title = o.title;\n        this._bounding.set(o.bounding);\n        this.color = o.color;\n        this.font_size = o.font_size;\n    };\n\n    LGraphGroup.prototype.serialize = function() {\n        var b = this._bounding;\n        return {\n            title: this.title,\n            bounding: [\n                Math.round(b[0]),\n                Math.round(b[1]),\n                Math.round(b[2]),\n                Math.round(b[3])\n            ],\n            color: this.color,\n            font_size: this.font_size\n        };\n    };\n\n    LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) {\n        this._pos[0] += deltax;\n        this._pos[1] += deltay;\n        if (ignore_nodes) {\n            return;\n        }\n        for (var i = 0; i < this._nodes.length; ++i) {\n            var node = this._nodes[i];\n            node.pos[0] += deltax;\n            node.pos[1] += deltay;\n        }\n    };\n\n    LGraphGroup.prototype.recomputeInsideNodes = function() {\n        this._nodes.length = 0;\n        var nodes = this.graph._nodes;\n        var node_bounding = new Float32Array(4);\n\n        for (var i = 0; i < nodes.length; ++i) {\n            var node = nodes[i];\n            node.getBounding(node_bounding);\n            if (!overlapBounding(this._bounding, node_bounding)) {\n                continue;\n            } //out of the visible area\n            this._nodes.push(node);\n        }\n    };\n\n    LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside;\n    LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas;\n\n    //****************************************\n\n    //Scale and Offset\n    function DragAndScale(element, skip_events) {\n        this.offset = new Float32Array([0, 0]);\n        this.scale = 1;\n        this.max_scale = 10;\n        this.min_scale = 0.1;\n        this.onredraw = null;\n        this.enabled = true;\n        this.last_mouse = [0, 0];\n        this.element = null;\n        this.visible_area = new Float32Array(4);\n\n        if (element) {\n            this.element = element;\n            if (!skip_events) {\n                this.bindEvents(element);\n            }\n        }\n    }\n\n    LiteGraph.DragAndScale = DragAndScale;\n\n    DragAndScale.prototype.bindEvents = function(element) {\n        this.last_mouse = new Float32Array(2);\n\n        this._binded_mouse_callback = this.onMouse.bind(this);\n\n\t\tLiteGraph.pointerListenerAdd(element,\"down\", this._binded_mouse_callback);\n\t\tLiteGraph.pointerListenerAdd(element,\"move\", this._binded_mouse_callback);\n\t\tLiteGraph.pointerListenerAdd(element,\"up\", this._binded_mouse_callback);\n\n        element.addEventListener(\n            \"mousewheel\",\n            this._binded_mouse_callback,\n            false\n        );\n        element.addEventListener(\"wheel\", this._binded_mouse_callback, false);\n    };\n\n    DragAndScale.prototype.computeVisibleArea = function( viewport ) {\n        if (!this.element) {\n            this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0;\n            return;\n        }\n        var width = this.element.width;\n        var height = this.element.height;\n        var startx = -this.offset[0];\n        var starty = -this.offset[1];\n\t\tif( viewport )\n\t\t{\n\t\t\tstartx += viewport[0] / this.scale;\n\t\t\tstarty += viewport[1] / this.scale;\n\t\t\twidth = viewport[2];\n\t\t\theight = viewport[3];\n\t\t}\n        var endx = startx + width / this.scale;\n        var endy = starty + height / this.scale;\n        this.visible_area[0] = startx;\n        this.visible_area[1] = starty;\n        this.visible_area[2] = endx - startx;\n        this.visible_area[3] = endy - starty;\n    };\n\n    DragAndScale.prototype.onMouse = function(e) {\n        if (!this.enabled) {\n            return;\n        }\n\n        var canvas = this.element;\n        var rect = canvas.getBoundingClientRect();\n        var x = e.clientX - rect.left;\n        var y = e.clientY - rect.top;\n        e.canvasx = x;\n        e.canvasy = y;\n        e.dragging = this.dragging;\n        \n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\n\t\t//console.log(\"pointerevents: DragAndScale onMouse \"+e.type+\" \"+is_inside);\n\t\t\n        var ignore = false;\n        if (this.onmouse) {\n            ignore = this.onmouse(e);\n        }\n\n        if (e.type == LiteGraph.pointerevents_method+\"down\" && is_inside) {\n            this.dragging = true;\n\t\t\tLiteGraph.pointerListenerRemove(canvas,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(document,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(document,\"up\",this._binded_mouse_callback);\n        } else if (e.type == LiteGraph.pointerevents_method+\"move\") {\n            if (!ignore) {\n                var deltax = x - this.last_mouse[0];\n                var deltay = y - this.last_mouse[1];\n                if (this.dragging) {\n                    this.mouseDrag(deltax, deltay);\n                }\n            }\n        } else if (e.type == LiteGraph.pointerevents_method+\"up\") {\n            this.dragging = false;\n\t\t\tLiteGraph.pointerListenerRemove(document,\"move\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerRemove(document,\"up\",this._binded_mouse_callback);\n\t\t\tLiteGraph.pointerListenerAdd(canvas,\"move\",this._binded_mouse_callback);\n        } else if ( is_inside &&\n            (e.type == \"mousewheel\" ||\n            e.type == \"wheel\" ||\n            e.type == \"DOMMouseScroll\")\n        ) {\n            e.eventType = \"mousewheel\";\n            if (e.type == \"wheel\") {\n                e.wheel = -e.deltaY;\n            } else {\n                e.wheel =\n                    e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60;\n            }\n\n            //from stack overflow\n            e.delta = e.wheelDelta\n                ? e.wheelDelta / 40\n                : e.deltaY\n                ? -e.deltaY / 3\n                : 0;\n            this.changeDeltaScale(1.0 + e.delta * 0.05);\n        }\n\n        this.last_mouse[0] = x;\n        this.last_mouse[1] = y;\n\n\t\tif(is_inside)\n\t\t{\n\t        e.preventDefault();\n\t\t    e.stopPropagation();\n\t\t    return false;\n\t\t}\n    };\n\n    DragAndScale.prototype.toCanvasContext = function(ctx) {\n        ctx.scale(this.scale, this.scale);\n        ctx.translate(this.offset[0], this.offset[1]);\n    };\n\n    DragAndScale.prototype.convertOffsetToCanvas = function(pos) {\n        //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]];\n        return [\n            (pos[0] + this.offset[0]) * this.scale,\n            (pos[1] + this.offset[1]) * this.scale\n        ];\n    };\n\n    DragAndScale.prototype.convertCanvasToOffset = function(pos, out) {\n        out = out || [0, 0];\n        out[0] = pos[0] / this.scale - this.offset[0];\n        out[1] = pos[1] / this.scale - this.offset[1];\n        return out;\n    };\n\n    DragAndScale.prototype.mouseDrag = function(x, y) {\n        this.offset[0] += x / this.scale;\n        this.offset[1] += y / this.scale;\n\n        if (this.onredraw) {\n            this.onredraw(this);\n        }\n    };\n\n    DragAndScale.prototype.changeScale = function(value, zooming_center) {\n        if (value < this.min_scale) {\n            value = this.min_scale;\n        } else if (value > this.max_scale) {\n            value = this.max_scale;\n        }\n\n        if (value == this.scale) {\n            return;\n        }\n\n        if (!this.element) {\n            return;\n        }\n\n        var rect = this.element.getBoundingClientRect();\n        if (!rect) {\n            return;\n        }\n\n        zooming_center = zooming_center || [\n            rect.width * 0.5,\n            rect.height * 0.5\n        ];\n        var center = this.convertCanvasToOffset(zooming_center);\n        this.scale = value;\n        if (Math.abs(this.scale - 1) < 0.01) {\n            this.scale = 1;\n        }\n\n        var new_center = this.convertCanvasToOffset(zooming_center);\n        var delta_offset = [\n            new_center[0] - center[0],\n            new_center[1] - center[1]\n        ];\n\n        this.offset[0] += delta_offset[0];\n        this.offset[1] += delta_offset[1];\n\n        if (this.onredraw) {\n            this.onredraw(this);\n        }\n    };\n\n    DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) {\n        this.changeScale(this.scale * value, zooming_center);\n    };\n\n    DragAndScale.prototype.reset = function() {\n        this.scale = 1;\n        this.offset[0] = 0;\n        this.offset[1] = 0;\n    };\n\n    //*********************************************************************************\n    // LGraphCanvas: LGraph renderer CLASS\n    //*********************************************************************************\n\n    /**\n     * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required.\n     * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked\n     *\n     * @class LGraphCanvas\n     * @constructor\n     * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself)\n     * @param {LGraph} graph [optional]\n     * @param {Object} options [optional] { skip_rendering, autoresize, viewport }\n     */\n    function LGraphCanvas(canvas, graph, options) {\n        this.options = options = options || {};\n\n        //if(graph === undefined)\n        //\tthrow (\"No graph assigned\");\n        this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE;\n\n        if (canvas && canvas.constructor === String) {\n            canvas = document.querySelector(canvas);\n        }\n\n        this.ds = new DragAndScale();\n        this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much\n\n        this.title_text_font = \"\" + LiteGraph.NODE_TEXT_SIZE + \"px Arial\";\n        this.inner_text_font =\n            \"normal \" + LiteGraph.NODE_SUBTEXT_SIZE + \"px Arial\";\n        this.node_title_color = LiteGraph.NODE_TITLE_COLOR;\n        this.default_link_color = LiteGraph.LINK_COLOR;\n        this.default_connection_color = {\n            input_off: \"#778\",\n            input_on: \"#7F7\", //\"#BBD\"\n            output_off: \"#778\",\n            output_on: \"#7F7\" //\"#BBD\"\n\t\t};\n        this.default_connection_color_byType = {\n            /*number: \"#7F7\",\n            string: \"#77F\",\n            boolean: \"#F77\",*/\n        }\n        this.default_connection_color_byTypeOff = {\n            /*number: \"#474\",\n            string: \"#447\",\n            boolean: \"#744\",*/\n        };\n\n        this.highquality_render = true;\n        this.use_gradients = false; //set to true to render titlebar with gradients\n        this.editor_alpha = 1; //used for transition\n        this.pause_rendering = false;\n        this.clear_background = true;\n        this.clear_background_color = \"#222\";\n\n\t\tthis.read_only = false; //if set to true users cannot modify the graph\n        this.render_only_selected = true;\n        this.live_mode = false;\n        this.show_info = true;\n        this.allow_dragcanvas = true;\n        this.allow_dragnodes = true;\n        this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc\n        this.multi_select = false; //allow selecting multi nodes without pressing extra keys\n        this.allow_searchbox = true;\n        this.allow_reconnect_links = true; //allows to change a connection with having to redo it again\n\t\tthis.align_to_grid = false; //snap to grid\n\n        this.drag_mode = false;\n        this.dragging_rectangle = null;\n\n        this.filter = null; //allows to filter to only accept some type of nodes in a graph\n\n\t\tthis.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything\n        this.always_render_background = false;\n        this.render_shadows = true;\n        this.render_canvas_border = true;\n        this.render_connections_shadows = false; //too much cpu\n        this.render_connections_border = true;\n        this.render_curved_connections = false;\n        this.render_connection_arrows = false;\n        this.render_collapsed_slots = true;\n        this.render_execution_order = false;\n        this.render_title_colored = true;\n\t\tthis.render_link_tooltip = true;\n\n        this.links_render_mode = LiteGraph.SPLINE_LINK;\n\n        this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle\n        this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle\n\t\tthis.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD\n\n        //to personalize the search box\n        this.onSearchBox = null;\n        this.onSearchBoxSelection = null;\n\n        //callbacks\n        this.onMouse = null;\n        this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform\n        this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform\n        this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs)\n\t\tthis.onDrawLinkTooltip = null; //called when rendering a tooltip\n\t\tthis.onNodeMoved = null; //called after moving a node\n\t\tthis.onSelectionChange = null; //called if the selection changes\n\t\tthis.onConnectingChange = null; //called before any link changes\n\t\tthis.onBeforeChange = null; //called before modifying the graph\n\t\tthis.onAfterChange = null; //called after modifying the graph\n\n        this.connections_width = 3;\n        this.round_radius = 8;\n\n        this.current_node = null;\n        this.node_widget = null; //used for widgets\n\t\tthis.over_link_center = null;\n        this.last_mouse_position = [0, 0];\n        this.visible_area = this.ds.visible_area;\n        this.visible_links = [];\n\n\t\tthis.viewport = options.viewport || null; //to constraint render area to a portion of the canvas\n\n        //link canvas and graph\n        if (graph) {\n            graph.attachCanvas(this);\n        }\n\n        this.setCanvas(canvas,options.skip_events);\n        this.clear();\n\n        if (!options.skip_render) {\n            this.startRendering();\n        }\n\n        this.autoresize = options.autoresize;\n    }\n\n    global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas;\n\n\tLGraphCanvas.DEFAULT_BACKGROUND_IMAGE = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=\";\n\n    LGraphCanvas.link_type_colors = {\n        \"-1\": LiteGraph.EVENT_LINK_COLOR,\n        number: \"#AAA\",\n        node: \"#DCA\"\n    };\n    LGraphCanvas.gradients = {}; //cache of gradients\n\n    /**\n     * clears all the data inside\n     *\n     * @method clear\n     */\n    LGraphCanvas.prototype.clear = function() {\n        this.frame = 0;\n        this.last_draw_time = 0;\n        this.render_time = 0;\n        this.fps = 0;\n\n        //this.scale = 1;\n        //this.offset = [0,0];\n\n        this.dragging_rectangle = null;\n\n        this.selected_nodes = {};\n        this.selected_group = null;\n\n        this.visible_nodes = [];\n        this.node_dragged = null;\n        this.node_over = null;\n        this.node_capturing_input = null;\n        this.connecting_node = null;\n        this.highlighted_links = {};\n\n\t\tthis.dragging_canvas = false;\n\n        this.dirty_canvas = true;\n        this.dirty_bgcanvas = true;\n        this.dirty_area = null;\n\n        this.node_in_panel = null;\n        this.node_widget = null;\n\n        this.last_mouse = [0, 0];\n        this.last_mouseclick = 0;\n\t  \tthis.pointer_is_down = false;\n\t  \tthis.pointer_is_double = false;\n        this.visible_area.set([0, 0, 0, 0]);\n\n        if (this.onClear) {\n            this.onClear();\n        }\n    };\n\n    /**\n     * assigns a graph, you can reassign graphs to the same canvas\n     *\n     * @method setGraph\n     * @param {LGraph} graph\n     */\n    LGraphCanvas.prototype.setGraph = function(graph, skip_clear) {\n        if (this.graph == graph) {\n            return;\n        }\n\n        if (!skip_clear) {\n            this.clear();\n        }\n\n        if (!graph && this.graph) {\n            this.graph.detachCanvas(this);\n            return;\n        }\n\n        graph.attachCanvas(this);\n\n\t\t//remove the graph stack in case a subgraph was open\n\t\tif (this._graph_stack)\n\t\t\tthis._graph_stack = null;\n\n        this.setDirty(true, true);\n    };\n\n    /**\n     * returns the top level graph (in case there are subgraphs open on the canvas)\n     *\n     * @method getTopGraph\n     * @return {LGraph} graph\n     */\n\tLGraphCanvas.prototype.getTopGraph = function()\n\t{\n\t\tif(this._graph_stack.length)\n\t\t\treturn this._graph_stack[0];\n\t\treturn this.graph;\n\t}\n\n    /**\n     * opens a graph contained inside a node in the current graph\n     *\n     * @method openSubgraph\n     * @param {LGraph} graph\n     */\n    LGraphCanvas.prototype.openSubgraph = function(graph) {\n        if (!graph) {\n            throw \"graph cannot be null\";\n        }\n\n        if (this.graph == graph) {\n            throw \"graph cannot be the same\";\n        }\n\n        this.clear();\n\n        if (this.graph) {\n            if (!this._graph_stack) {\n                this._graph_stack = [];\n            }\n            this._graph_stack.push(this.graph);\n        }\n\n        graph.attachCanvas(this);\n\t\tthis.checkPanels();\n        this.setDirty(true, true);\n    };\n\n    /**\n     * closes a subgraph contained inside a node\n     *\n     * @method closeSubgraph\n     * @param {LGraph} assigns a graph\n     */\n    LGraphCanvas.prototype.closeSubgraph = function() {\n        if (!this._graph_stack || this._graph_stack.length == 0) {\n            return;\n        }\n        var subgraph_node = this.graph._subgraph_node;\n        var graph = this._graph_stack.pop();\n        this.selected_nodes = {};\n        this.highlighted_links = {};\n        graph.attachCanvas(this);\n        this.setDirty(true, true);\n        if (subgraph_node) {\n            this.centerOnNode(subgraph_node);\n            this.selectNodes([subgraph_node]);\n        }\n        // when close sub graph back to offset [0, 0] scale 1\n        this.ds.offset = [0, 0]\n        this.ds.scale = 1\n    };\n\n    /**\n     * returns the visually active graph (in case there are more in the stack)\n     * @method getCurrentGraph\n     * @return {LGraph} the active graph\n     */\n    LGraphCanvas.prototype.getCurrentGraph = function() {\n        return this.graph;\n    };\n\n    /**\n     * assigns a canvas\n     *\n     * @method setCanvas\n     * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector)\n     */\n    LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) {\n        var that = this;\n\n        if (canvas) {\n            if (canvas.constructor === String) {\n                canvas = document.getElementById(canvas);\n                if (!canvas) {\n                    throw \"Error creating LiteGraph canvas: Canvas not found\";\n                }\n            }\n        }\n\n        if (canvas === this.canvas) {\n            return;\n        }\n\n        if (!canvas && this.canvas) {\n            //maybe detach events from old_canvas\n            if (!skip_events) {\n                this.unbindEvents();\n            }\n        }\n\n        this.canvas = canvas;\n        this.ds.element = canvas;\n\n        if (!canvas) {\n            return;\n        }\n\n        //this.canvas.tabindex = \"1000\";\n        canvas.className += \" lgraphcanvas\";\n        canvas.data = this;\n        canvas.tabindex = \"1\"; //to allow key events\n\n        //bg canvas: used for non changing stuff\n        this.bgcanvas = null;\n        if (!this.bgcanvas) {\n            this.bgcanvas = document.createElement(\"canvas\");\n            this.bgcanvas.width = this.canvas.width;\n            this.bgcanvas.height = this.canvas.height;\n        }\n\n        if (canvas.getContext == null) {\n            if (canvas.localName != \"canvas\") {\n                throw \"Element supplied for LGraphCanvas must be a <canvas> element, you passed a \" +\n                    canvas.localName;\n            }\n            throw \"This browser doesn't support Canvas\";\n        }\n\n        var ctx = (this.ctx = canvas.getContext(\"2d\"));\n        if (ctx == null) {\n            if (!canvas.webgl_enabled) {\n                console.warn(\n                    \"This canvas seems to be WebGL, enabling WebGL renderer\"\n                );\n            }\n            this.enableWebGL();\n        }\n\n        //input:  (move and up could be unbinded)\n        // why here? this._mousemove_callback = this.processMouseMove.bind(this);\n        // why here? this._mouseup_callback = this.processMouseUp.bind(this);\n\n        if (!skip_events) {\n            this.bindEvents();\n        }\n    };\n\n    //used in some events to capture them\n    LGraphCanvas.prototype._doNothing = function doNothing(e) {\n    \t//console.log(\"pointerevents: _doNothing \"+e.type);\n        e.preventDefault();\n        return false;\n    };\n    LGraphCanvas.prototype._doReturnTrue = function doNothing(e) {\n        e.preventDefault();\n        return true;\n    };\n\n    /**\n     * binds mouse, keyboard, touch and drag events to the canvas\n     * @method bindEvents\n     **/\n    LGraphCanvas.prototype.bindEvents = function() {\n        if (this._events_binded) {\n            console.warn(\"LGraphCanvas: events already binded\");\n            return;\n        }\n\n        //console.log(\"pointerevents: bindEvents\");\n        \n        var canvas = this.canvas;\n\n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document; //hack used when moving canvas between windows\n\n        this._mousedown_callback = this.processMouseDown.bind(this);\n        this._mousewheel_callback = this.processMouseWheel.bind(this);\n        // why mousemove and mouseup were not binded here?\n        this._mousemove_callback = this.processMouseMove.bind(this);\n        this._mouseup_callback = this.processMouseUp.bind(this);\n        \n        //touch events -- TODO IMPLEMENT\n        //this._touch_callback = this.touchHandler.bind(this);\n\n\t\tLiteGraph.pointerListenerAdd(canvas,\"down\", this._mousedown_callback, true); //down do not need to store the binded\n        canvas.addEventListener(\"mousewheel\", this._mousewheel_callback, false);\n\n        LiteGraph.pointerListenerAdd(canvas,\"up\", this._mouseup_callback, true); // CHECK: ??? binded or not\n\t\tLiteGraph.pointerListenerAdd(canvas,\"move\", this._mousemove_callback);\n        \n        canvas.addEventListener(\"contextmenu\", this._doNothing);\n        canvas.addEventListener(\n            \"DOMMouseScroll\",\n            this._mousewheel_callback,\n            false\n        );\n\n        //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents\n        /*if( 'touchstart' in document.documentElement )\n        {\n            canvas.addEventListener(\"touchstart\", this._touch_callback, true);\n            canvas.addEventListener(\"touchmove\", this._touch_callback, true);\n            canvas.addEventListener(\"touchend\", this._touch_callback, true);\n            canvas.addEventListener(\"touchcancel\", this._touch_callback, true);\n        }*/\n\n        //Keyboard ******************\n        this._key_callback = this.processKey.bind(this);\n        canvas.setAttribute(\"tabindex\",1); //otherwise key events are ignored\n        canvas.addEventListener(\"keydown\", this._key_callback, true);\n        document.addEventListener(\"keyup\", this._key_callback, true); //in document, otherwise it doesn't fire keyup\n\n        //Dropping Stuff over nodes ************************************\n        this._ondrop_callback = this.processDrop.bind(this);\n\n        canvas.addEventListener(\"dragover\", this._doNothing, false);\n        canvas.addEventListener(\"dragend\", this._doNothing, false);\n        canvas.addEventListener(\"drop\", this._ondrop_callback, false);\n        canvas.addEventListener(\"dragenter\", this._doReturnTrue, false);\n\n        this._events_binded = true;\n    };\n\n    /**\n     * unbinds mouse events from the canvas\n     * @method unbindEvents\n     **/\n    LGraphCanvas.prototype.unbindEvents = function() {\n        if (!this._events_binded) {\n            console.warn(\"LGraphCanvas: no events binded\");\n            return;\n        }\n\n        //console.log(\"pointerevents: unbindEvents\");\n        \n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document;\n\n\t\tLiteGraph.pointerListenerRemove(this.canvas,\"move\", this._mousedown_callback);\n        LiteGraph.pointerListenerRemove(this.canvas,\"up\", this._mousedown_callback);\n        LiteGraph.pointerListenerRemove(this.canvas,\"down\", this._mousedown_callback);\n        this.canvas.removeEventListener(\n            \"mousewheel\",\n            this._mousewheel_callback\n        );\n        this.canvas.removeEventListener(\n            \"DOMMouseScroll\",\n            this._mousewheel_callback\n        );\n        this.canvas.removeEventListener(\"keydown\", this._key_callback);\n        document.removeEventListener(\"keyup\", this._key_callback);\n        this.canvas.removeEventListener(\"contextmenu\", this._doNothing);\n        this.canvas.removeEventListener(\"drop\", this._ondrop_callback);\n        this.canvas.removeEventListener(\"dragenter\", this._doReturnTrue);\n\n        //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents\n        /*this.canvas.removeEventListener(\"touchstart\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchmove\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchend\", this._touch_callback );\n        this.canvas.removeEventListener(\"touchcancel\", this._touch_callback );*/\n\n        this._mousedown_callback = null;\n        this._mousewheel_callback = null;\n        this._key_callback = null;\n        this._ondrop_callback = null;\n\n        this._events_binded = false;\n    };\n\n    LGraphCanvas.getFileExtension = function(url) {\n        var question = url.indexOf(\"?\");\n        if (question != -1) {\n            url = url.substr(0, question);\n        }\n        var point = url.lastIndexOf(\".\");\n        if (point == -1) {\n            return \"\";\n        }\n        return url.substr(point + 1).toLowerCase();\n    };\n\n    /**\n     * this function allows to render the canvas using WebGL instead of Canvas2D\n     * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL\n     * @method enableWebGL\n     **/\n    LGraphCanvas.prototype.enableWebGL = function() {\n        if (typeof GL === \"undefined\") {\n            throw \"litegl.js must be included to use a WebGL canvas\";\n        }\n        if (typeof enableWebGLCanvas === \"undefined\") {\n            throw \"webglCanvas.js must be included to use this feature\";\n        }\n\n        this.gl = this.ctx = enableWebGLCanvas(this.canvas);\n        this.ctx.webgl = true;\n        this.bgcanvas = this.canvas;\n        this.bgctx = this.gl;\n        this.canvas.webgl_enabled = true;\n\n        /*\n\tGL.create({ canvas: this.bgcanvas });\n\tthis.bgctx = enableWebGLCanvas( this.bgcanvas );\n\twindow.gl = this.gl;\n\t*/\n    };\n\n    /**\n     * marks as dirty the canvas, this way it will be rendered again\n     *\n     * @class LGraphCanvas\n     * @method setDirty\n     * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes)\n     * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires)\n     */\n    LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) {\n        if (fgcanvas) {\n            this.dirty_canvas = true;\n        }\n        if (bgcanvas) {\n            this.dirty_bgcanvas = true;\n        }\n    };\n\n    /**\n     * Used to attach the canvas in a popup\n     *\n     * @method getCanvasWindow\n     * @return {window} returns the window where the canvas is attached (the DOM root node)\n     */\n    LGraphCanvas.prototype.getCanvasWindow = function() {\n        if (!this.canvas) {\n            return window;\n        }\n        var doc = this.canvas.ownerDocument;\n        return doc.defaultView || doc.parentWindow;\n    };\n\n    /**\n     * starts rendering the content of the canvas when needed\n     *\n     * @method startRendering\n     */\n    LGraphCanvas.prototype.startRendering = function() {\n        if (this.is_rendering) {\n            return;\n        } //already rendering\n\n        this.is_rendering = true;\n        renderFrame.call(this);\n\n        function renderFrame() {\n            if (!this.pause_rendering) {\n                this.draw();\n            }\n\n            var window = this.getCanvasWindow();\n            if (this.is_rendering) {\n                window.requestAnimationFrame(renderFrame.bind(this));\n            }\n        }\n    };\n\n    /**\n     * stops rendering the content of the canvas (to save resources)\n     *\n     * @method stopRendering\n     */\n    LGraphCanvas.prototype.stopRendering = function() {\n        this.is_rendering = false;\n        /*\n\tif(this.rendering_timer_id)\n\t{\n\t\tclearInterval(this.rendering_timer_id);\n\t\tthis.rendering_timer_id = null;\n\t}\n\t*/\n    };\n\n    /* LiteGraphCanvas input */\n\n\t//used to block future mouse events (because of im gui)\n\tLGraphCanvas.prototype.blockClick = function()\n\t{\n\t\tthis.block_click = true;\n\t\tthis.last_mouseclick = 0;\n\t}\n\t\n    LGraphCanvas.prototype.processMouseDown = function(e) {\n    \t\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\t\t\n\t\tif (!this.graph) {\n            return;\n        }\n\n        this.adjustMouseEvent(e);\n\n        var ref_window = this.getCanvasWindow();\n        var document = ref_window.document;\n        LGraphCanvas.active_canvas = this;\n        var that = this;\n\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\t//console.log(y,this.viewport);\n\t\t//console.log(\"pointerevents: processMouseDown pointerId:\"+e.pointerId+\" which:\"+e.which+\" isPrimary:\"+e.isPrimary+\" :: x y \"+x+\" \"+y);\n\n\t\tthis.ds.viewport = this.viewport;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\n        //move mouse move event to the window in case it drags outside of the canvas\n\t\tif(!this.options.skip_events)\n\t\t{\n\t\t\tLiteGraph.pointerListenerRemove(this.canvas,\"move\", this._mousemove_callback);\n\t\t\tLiteGraph.pointerListenerAdd(ref_window.document,\"move\", this._mousemove_callback,true); //catch for the entire window\n\t\t\tLiteGraph.pointerListenerAdd(ref_window.document,\"up\", this._mouseup_callback,true);\n\t\t}\n\n\t\tif(!is_inside){\n\t\t\treturn;\n\t\t}\n\n        var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 );\n        var skip_dragging = false;\n        var skip_action = false;\n        var now = LiteGraph.getTime();\n\t\tvar is_primary = (e.isPrimary === undefined || !e.isPrimary);\n        var is_double_click = (now - this.last_mouseclick < 300) && is_primary;\n\t\tthis.mouse[0] = e.clientX;\n\t\tthis.mouse[1] = e.clientY;\n        this.graph_mouse[0] = e.canvasX;\n        this.graph_mouse[1] = e.canvasY;\n\t\tthis.last_click_position = [this.mouse[0],this.mouse[1]];\n\t  \t\n\t  \tif (this.pointer_is_down && is_primary ){\n\t\t  this.pointer_is_double = true;\n\t\t  //console.log(\"pointerevents: pointer_is_double start\");\n\t\t}else{\n\t\t  this.pointer_is_double = false;\n\t\t}\n\t  \tthis.pointer_is_down = true;\n\t  \n\t  \t\n        this.canvas.focus();\n\n        LiteGraph.closeAllContextMenus(ref_window);\n\n        if (this.onMouse)\n\t\t{\n            if (this.onMouse(e) == true)\n                return;\n        }\n\n\t\t//left button mouse / single finger\n        if (e.which == 1 && !this.pointer_is_double)\n\t\t{\n            if (e.ctrlKey)\n\t\t\t{\n                this.dragging_rectangle = new Float32Array(4);\n                this.dragging_rectangle[0] = e.canvasX;\n                this.dragging_rectangle[1] = e.canvasY;\n                this.dragging_rectangle[2] = 1;\n                this.dragging_rectangle[3] = 1;\n                skip_action = true;\n            }\n\n            // clone node ALT dragging\n            if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && node && this.allow_interaction && !skip_action && !this.read_only)\n            {\n                if (cloned = node.clone()){\n                    cloned.pos[0] += 5;\n                    cloned.pos[1] += 5;\n                    this.graph.add(cloned,false,{doCalcSize: false});\n                    node = cloned;\n                    skip_action = true;\n                    if (!block_drag_node) {\n                        if (this.allow_dragnodes) {\n\t\t\t\t\t\t\tthis.graph.beforeChange();\n                            this.node_dragged = node;\n                        }\n                        if (!this.selected_nodes[node.id]) {\n                            this.processNodeSelected(node, e);\n                        }\n                    }\n                }\n            }\n            \n            var clicking_canvas_bg = false;\n\n            //when clicked on top of a node\n            //and it is not interactive\n            if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) {\n                if (!this.live_mode && !node.flags.pinned) {\n                    this.bringToFront(node);\n                } //if it wasn't selected?\n\n                //not dragging mouse to connect two slots\n                if ( this.allow_interaction && !this.connecting_node && !node.flags.collapsed && !this.live_mode ) {\n                    //Search for corner for resize\n                    if ( !skip_action &&\n                        node.resizable !== false &&\n                        isInsideRectangle( e.canvasX,\n                            e.canvasY,\n                            node.pos[0] + node.size[0] - 5,\n                            node.pos[1] + node.size[1] - 5,\n                            10,\n                            10\n                        )\n                    ) {\n\t\t\t\t\t\tthis.graph.beforeChange();\n                        this.resizing_node = node;\n                        this.canvas.style.cursor = \"se-resize\";\n                        skip_action = true;\n                    } else {\n                        //search for outputs\n                        if (node.outputs) {\n                            for ( var i = 0, l = node.outputs.length; i < l; ++i ) {\n                                var output = node.outputs[i];\n                                var link_pos = node.getConnectionPos(false, i);\n                                if (\n                                    isInsideRectangle(\n                                        e.canvasX,\n                                        e.canvasY,\n                                        link_pos[0] - 15,\n                                        link_pos[1] - 10,\n                                        30,\n                                        20\n                                    )\n                                ) {\n                                    this.connecting_node = node;\n                                    this.connecting_output = output;\n                                    this.connecting_output.slot_index = i;\n                                    this.connecting_pos = node.getConnectionPos( false, i );\n                                    this.connecting_slot = i;\n\n                                    if (LiteGraph.shift_click_do_break_link_from){\n                                        if (e.shiftKey) {\n                                            node.disconnectOutput(i);\n                                        }\n                                    }\n\n                                    if (is_double_click) {\n                                        if (node.onOutputDblClick) {\n                                            node.onOutputDblClick(i, e);\n                                        }\n                                    } else {\n                                        if (node.onOutputClick) {\n                                            node.onOutputClick(i, e);\n                                        }\n                                    }\n\n                                    skip_action = true;\n                                    break;\n                                }\n                            }\n                        }\n\n                        //search for inputs\n                        if (node.inputs) {\n                            for ( var i = 0, l = node.inputs.length; i < l; ++i ) {\n                                var input = node.inputs[i];\n                                var link_pos = node.getConnectionPos(true, i);\n                                if (\n                                    isInsideRectangle(\n                                        e.canvasX,\n                                        e.canvasY,\n                                        link_pos[0] - 15,\n                                        link_pos[1] - 10,\n                                        30,\n                                        20\n                                    )\n                                ) {\n                                    if (is_double_click) {\n                                        if (node.onInputDblClick) {\n                                            node.onInputDblClick(i, e);\n                                        }\n                                    } else {\n                                        if (node.onInputClick) {\n                                            node.onInputClick(i, e);\n                                        }\n                                    }\n\n                                    if (input.link !== null) {\n                                        var link_info = this.graph.links[\n                                            input.link\n                                        ]; //before disconnecting\n                                        if (LiteGraph.click_do_break_link_to){\n                                            node.disconnectInput(i);\n                                            this.dirty_bgcanvas = true;\n                                            skip_action = true;\n                                        }else{\n                                            // do same action as has not node ?\n                                        }\n\n                                        if (\n                                            this.allow_reconnect_links ||\n\t\t\t\t\t\t\t\t\t\t\t//this.move_destination_link_without_shift ||\n                                            e.shiftKey\n                                        ) {\n                                            if (!LiteGraph.click_do_break_link_to){\n                                                node.disconnectInput(i);\n                                            }\n                                            this.connecting_node = this.graph._nodes_by_id[\n                                                link_info.origin_id\n                                            ];\n                                            this.connecting_slot =\n                                                link_info.origin_slot;\n                                            this.connecting_output = this.connecting_node.outputs[\n                                                this.connecting_slot\n                                            ];\n                                            this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot );\n                                            \n                                            this.dirty_bgcanvas = true;\n                                            skip_action = true;\n                                        }\n\n                                        \n                                    }else{\n                                        // has not node\n                                    }\n                                    \n                                    if (!skip_action){\n                                        // connect from in to out, from to to from\n                                        this.connecting_node = node;\n                                        this.connecting_input = input;\n                                        this.connecting_input.slot_index = i;\n                                        this.connecting_pos = node.getConnectionPos( true, i );\n                                        this.connecting_slot = i;\n                                        \n                                        this.dirty_bgcanvas = true;\n                                        skip_action = true;\n                                    }\n                                }\n                            }\n                        }\n                    } //not resizing\n                }\n\n                //it wasn't clicked on the links boxes\n                if (!skip_action) {\n                    var block_drag_node = false;\n\t\t\t\t\tvar pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]];\n\n                    //widgets\n                    var widget = this.processNodeWidgets( node, this.graph_mouse, e );\n                    if (widget) {\n                        block_drag_node = true;\n                        this.node_widget = [node, widget];\n                    }\n\n                    //double clicking\n                    if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) {\n                        //double click node\n                        if (node.onDblClick) {\n                            node.onDblClick( e, pos, this );\n                        }\n                        this.processNodeDblClicked(node);\n                        block_drag_node = true;\n                    }\n\n                    //if do not capture mouse\n                    if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) {\n                        block_drag_node = true;\n                    } else {\n\t\t\t\t\t\t//open subgraph button\n\t\t\t\t\t\tif(node.subgraph && !node.skip_subgraph_button)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) {\n\t\t\t\t\t\t\t\tvar that = this;\n\t\t\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\t\t\tthat.openSubgraph(node.subgraph);\n\t\t\t\t\t\t\t\t}, 10);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (this.live_mode) {\n\t\t\t\t\t\t\tclicking_canvas_bg = true;\n\t                        block_drag_node = true;\n\t\t\t\t\t\t}\n                    }\n\n                    if (!block_drag_node) {\n                        if (this.allow_dragnodes) {\n\t\t\t\t\t\t\tthis.graph.beforeChange();\n                            this.node_dragged = node;\n                        }\n                        this.processNodeSelected(node, e);\n                    } else { // double-click\n                        /**\n                         * Don't call the function if the block is already selected.\n                         * Otherwise, it could cause the block to be unselected while its panel is open.\n                         */\n                        if (!node.is_selected) this.processNodeSelected(node, e);\n                    }\n\n                    this.dirty_canvas = true;\n                }\n            } //clicked outside of nodes\n            else {\n\t\t\t\tif (!skip_action){\n\t\t\t\t\t//search for link connector\n\t\t\t\t\tif(!this.read_only) {\n\t\t\t\t\t\tfor (var i = 0; i < this.visible_links.length; ++i) {\n\t\t\t\t\t\t\tvar link = this.visible_links[i];\n\t\t\t\t\t\t\tvar center = link._pos;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!center ||\n\t\t\t\t\t\t\t\te.canvasX < center[0] - 4 ||\n\t\t\t\t\t\t\t\te.canvasX > center[0] + 4 ||\n\t\t\t\t\t\t\t\te.canvasY < center[1] - 4 ||\n\t\t\t\t\t\t\t\te.canvasY > center[1] + 4\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t//link clicked\n\t\t\t\t\t\t\tthis.showLinkMenu(link, e);\n\t\t\t\t\t\t\tthis.over_link_center = null; //clear tooltip\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY );\n\t\t\t\t\tthis.selected_group_resizing = false;\n\t\t\t\t\tif (this.selected_group && !this.read_only ) {\n\t\t\t\t\t\tif (e.ctrlKey) {\n\t\t\t\t\t\t\tthis.dragging_rectangle = null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] );\n\t\t\t\t\t\tif (dist * this.ds.scale < 10) {\n\t\t\t\t\t\t\tthis.selected_group_resizing = true;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.selected_group.recomputeInsideNodes();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (is_double_click && !this.read_only && this.allow_searchbox) {\n\t\t\t\t\t\tthis.showSearchBox(e);\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t}\n\n\t\t\t\t\tclicking_canvas_bg = true;\n\t\t\t\t}\n            }\n\n            if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) {\n            \t//console.log(\"pointerevents: dragging_canvas start\");\n            \tthis.dragging_canvas = true;\n            }\n            \n        } else if (e.which == 2) {\n            //middle button\n        \t\n\t\t\tif (LiteGraph.middle_click_slot_add_default_node){\n\t\t\t\tif (node && this.allow_interaction && !skip_action && !this.read_only){\n\t\t\t\t\t//not dragging mouse to connect two slots\n\t\t\t\t\tif (\n\t\t\t\t\t\t!this.connecting_node &&\n\t\t\t\t\t\t!node.flags.collapsed &&\n\t\t\t\t\t\t!this.live_mode\n\t\t\t\t\t) {\n\t\t\t\t\t\tvar mClikSlot = false;\n\t\t\t\t\t\tvar mClikSlot_index = false;\n\t\t\t\t\t\tvar mClikSlot_isOut = false;\n\t\t\t\t\t\t//search for outputs\n\t\t\t\t\t\tif (node.outputs) {\n\t\t\t\t\t\t\tfor ( var i = 0, l = node.outputs.length; i < l; ++i ) {\n\t\t\t\t\t\t\t\tvar output = node.outputs[i];\n\t\t\t\t\t\t\t\tvar link_pos = node.getConnectionPos(false, i);\n\t\t\t\t\t\t\t\tif (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) {\n\t\t\t\t\t\t\t\t\tmClikSlot = output;\n\t\t\t\t\t\t\t\t\tmClikSlot_index = i;\n\t\t\t\t\t\t\t\t\tmClikSlot_isOut = true;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t//search for inputs\n\t\t\t\t\t\tif (node.inputs) {\n\t\t\t\t\t\t\tfor ( var i = 0, l = node.inputs.length; i < l; ++i ) {\n\t\t\t\t\t\t\t\tvar input = node.inputs[i];\n\t\t\t\t\t\t\t\tvar link_pos = node.getConnectionPos(true, i);\n\t\t\t\t\t\t\t\tif (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) {\n\t\t\t\t\t\t\t\t\tmClikSlot = input;\n\t\t\t\t\t\t\t\t\tmClikSlot_index = i;\n\t\t\t\t\t\t\t\t\tmClikSlot_isOut = false;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//console.log(\"middleClickSlots? \"+mClikSlot+\" & \"+(mClikSlot_index!==false));\n\t\t\t\t\t\tif (mClikSlot && mClikSlot_index!==false){\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tvar alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length)));\n\t\t\t\t\t\t\tvar node_bounding = node.getBounding();\n\t\t\t\t\t\t\t// estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes\n\t\t\t\t\t\t\tvar posRef = [\t(!mClikSlot_isOut?node_bounding[0]:node_bounding[0]+node_bounding[2])// + node_bounding[0]/this.canvas.width*150\n\t\t\t\t\t\t\t\t\t\t\t,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical \"derive\"\n\t\t\t\t\t\t\t\t\t\t  ];\n\t\t\t\t\t\t\tvar nodeCreated = this.createDefaultNodeForSlot({   \tnodeFrom: !mClikSlot_isOut?null:node\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotFrom: !mClikSlot_isOut?null:mClikSlot_index\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeTo: !mClikSlot_isOut?node:null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotTo: !mClikSlot_isOut?mClikSlot_index:null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,position: posRef //,e: e\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: \"AUTO\" //nodeNewType\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,posAdd:[!mClikSlot_isOut?-30:30, -alphaPosY*130] //-alphaPosY*30]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,posSizeFix:[!mClikSlot_isOut?-1:0, 0] //-alphaPosY*2*/\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (!skip_action && this.allow_dragcanvas) {\n            \t//console.log(\"pointerevents: dragging_canvas start from middle button\");\n            \tthis.dragging_canvas = true;\n            }\n\n        \t\n        } else if (e.which == 3 || this.pointer_is_double) {\n\t\t\t\n            //right button\n\t\t\tif (this.allow_interaction && !skip_action && !this.read_only){\n\t\t\t\t\n\t\t\t\t// is it hover a node ?\n\t\t\t\tif (node){\n\t\t\t\t\tif(Object.keys(this.selected_nodes).length\n\t\t\t\t\t   && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey)\n\t\t\t\t\t){\n\t\t\t\t\t\t// is multiselected or using shift to include the now node\n\t\t\t\t\t\tif (!this.selected_nodes[node.id]) this.selectNodes([node],true); // add this if not present\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// update selection\n\t\t\t\t\t\tthis.selectNodes([node]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// show menu on this node\n\t\t\t\tthis.processContextMenu(node, e);\n\t\t\t}\n\t\t\t\n        }\n\n        //TODO\n        //if(this.node_selected != prev_selected)\n        //\tthis.onNodeSelectionChange(this.node_selected);\n\n        this.last_mouse[0] = e.clientX;\n        this.last_mouse[1] = e.clientY;\n        this.last_mouseclick = LiteGraph.getTime();\n        this.last_mouse_dragging = true;\n\n        /*\n\tif( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)\n\t\tthis.draw();\n\t*/\n\n        this.graph.change();\n\n        //this is to ensure to defocus(blur) if a text input element is on focus\n        if (\n            !ref_window.document.activeElement ||\n            (ref_window.document.activeElement.nodeName.toLowerCase() !=\n                \"input\" &&\n                ref_window.document.activeElement.nodeName.toLowerCase() !=\n                    \"textarea\")\n        ) {\n            e.preventDefault();\n        }\n        e.stopPropagation();\n\n        if (this.onMouseDown) {\n            this.onMouseDown(e);\n        }\n\n        return false;\n    };\n\n    /**\n     * Called when a mouse move event has to be processed\n     * @method processMouseMove\n     **/\n    LGraphCanvas.prototype.processMouseMove = function(e) {\n        if (this.autoresize) {\n            this.resize();\n        }\n\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\n        if (!this.graph) {\n            return;\n        }\n\n        LGraphCanvas.active_canvas = this;\n        this.adjustMouseEvent(e);\n        var mouse = [e.clientX, e.clientY];\n\t\tthis.mouse[0] = mouse[0];\n\t\tthis.mouse[1] = mouse[1];\n        var delta = [\n            mouse[0] - this.last_mouse[0],\n            mouse[1] - this.last_mouse[1]\n        ];\n        this.last_mouse = mouse;\n        this.graph_mouse[0] = e.canvasX;\n        this.graph_mouse[1] = e.canvasY;\n\n        //console.log(\"pointerevents: processMouseMove \"+e.pointerId+\" \"+e.isPrimary);\n        \n\t\tif(this.block_click)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseMove block_click\");\n\t\t\te.preventDefault();\n\t\t\treturn false;\n\t\t}\n\n        e.dragging = this.last_mouse_dragging;\n\n        if (this.node_widget) {\n            this.processNodeWidgets(\n                this.node_widget[0],\n                this.graph_mouse,\n                e,\n                this.node_widget[1]\n            );\n            this.dirty_canvas = true;\n        }\n\n        //get node over\n        var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes);\n\n        if (this.dragging_rectangle)\n\t\t{\n            this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0];\n            this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1];\n            this.dirty_canvas = true;\n        } \n\t\telse if (this.selected_group && !this.read_only)\n\t\t{\n            //moving/resizing a group\n            if (this.selected_group_resizing) {\n                this.selected_group.size = [\n                    e.canvasX - this.selected_group.pos[0],\n                    e.canvasY - this.selected_group.pos[1]\n                ];\n            } else {\n                var deltax = delta[0] / this.ds.scale;\n                var deltay = delta[1] / this.ds.scale;\n                this.selected_group.move(deltax, deltay, e.ctrlKey);\n                if (this.selected_group._nodes.length) {\n                    this.dirty_canvas = true;\n                }\n            }\n            this.dirty_bgcanvas = true;\n        } else if (this.dragging_canvas) {\n        \t////console.log(\"pointerevents: processMouseMove is dragging_canvas\");\n            this.ds.offset[0] += delta[0] / this.ds.scale;\n            this.ds.offset[1] += delta[1] / this.ds.scale;\n            this.dirty_canvas = true;\n            this.dirty_bgcanvas = true;\n        } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) {\n            if (this.connecting_node) {\n                this.dirty_canvas = true;\n            }\n\n            //remove mouseover flag\n            for (var i = 0, l = this.graph._nodes.length; i < l; ++i) {\n                if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) {\n                    //mouse leave\n                    this.graph._nodes[i].mouseOver = false;\n                    if (this.node_over && this.node_over.onMouseLeave) {\n                        this.node_over.onMouseLeave(e);\n                    }\n                    this.node_over = null;\n                    this.dirty_canvas = true;\n                }\n            }\n\n            //mouse over a node\n            if (node) {\n\n\t\t\t\tif(node.redraw_on_mouse)\n                    this.dirty_canvas = true;\n\n                //this.canvas.style.cursor = \"move\";\n                if (!node.mouseOver) {\n                    //mouse enter\n                    node.mouseOver = true;\n                    this.node_over = node;\n                    this.dirty_canvas = true;\n\n                    if (node.onMouseEnter) {\n                        node.onMouseEnter(e);\n                    }\n                }\n\n                //in case the node wants to do something\n                if (node.onMouseMove) {\n                    node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this );\n                }\n\n                //if dragging a link\n                if (this.connecting_node) {\n                    \n                    if (this.connecting_output){\n                        \n                        var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput\n\n                        //on top of input\n                        if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {\n                            //mouse on top of the corner box, don't know what to do\n                        } else {\n                            //check if I have a slot below de mouse\n                            var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos );\n                            if (slot != -1 && node.inputs[slot]) {\n                                var slot_type = node.inputs[slot].type;\n                                if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) {\n                                    this._highlight_input = pos;\n\t\t\t\t\t\t\t\t\tthis._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS\n                                }\n                            } else {\n                                this._highlight_input = null;\n\t\t\t\t\t\t\t\tthis._highlight_input_slot = null;  // XXX CHECK THIS\n                            }\n                        }\n                        \n                    }else if(this.connecting_input){\n                        \n                        var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput\n\n                        //on top of output\n                        if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) {\n                            //mouse on top of the corner box, don't know what to do\n                        } else {\n                            //check if I have a slot below de mouse\n                            var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos );\n                            if (slot != -1 && node.outputs[slot]) {\n                                var slot_type = node.outputs[slot].type;\n                                if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) {\n                                    this._highlight_output = pos;\n                                }\n                            } else {\n                                this._highlight_output = null;\n                            }\n                        }\n                    }\n                }\n\n                //Search for corner\n                if (this.canvas) {\n                    if (\n                        isInsideRectangle(\n                            e.canvasX,\n                            e.canvasY,\n                            node.pos[0] + node.size[0] - 5,\n                            node.pos[1] + node.size[1] - 5,\n                            5,\n                            5\n                        )\n                    ) {\n                        this.canvas.style.cursor = \"se-resize\";\n                    } else {\n                        this.canvas.style.cursor = \"crosshair\";\n                    }\n                }\n            } else { //not over a node\n\n                //search for link connector\n\t\t\t\tvar over_link = null;\n\t\t\t\tfor (var i = 0; i < this.visible_links.length; ++i) {\n\t\t\t\t\tvar link = this.visible_links[i];\n\t\t\t\t\tvar center = link._pos;\n\t\t\t\t\tif (\n\t\t\t\t\t\t!center ||\n\t\t\t\t\t\te.canvasX < center[0] - 4 ||\n\t\t\t\t\t\te.canvasX > center[0] + 4 ||\n\t\t\t\t\t\te.canvasY < center[1] - 4 ||\n\t\t\t\t\t\te.canvasY > center[1] + 4\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tover_link = link;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif( over_link != this.over_link_center )\n\t\t\t\t{\n\t\t\t\t\tthis.over_link_center = over_link;\n\t                this.dirty_canvas = true;\n\t\t\t\t}\n\n\t\t\t\tif (this.canvas) {\n\t                this.canvas.style.cursor = \"\";\n\t\t\t\t}\n\t\t\t} //end\n\n\t\t\t//send event to node if capturing input (used with widgets that allow drag outside of the area of the node)\n            if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) {\n                this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this);\n            }\n\n\t\t\t//node being dragged\n            if (this.node_dragged && !this.live_mode) {\n\t\t\t\t//console.log(\"draggin!\",this.selected_nodes);\n                for (var i in this.selected_nodes) {\n                    var n = this.selected_nodes[i];\n                    n.pos[0] += delta[0] / this.ds.scale;\n                    n.pos[1] += delta[1] / this.ds.scale;\n                    if (!n.is_selected) this.processNodeSelected(n, e); /*\n                     * Don't call the function if the block is already selected.\n                     * Otherwise, it could cause the block to be unselected while dragging.\n                     */\n                }\n\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n            }\n\n            if (this.resizing_node && !this.live_mode) {\n                //convert mouse to node space\n\t\t\t\tvar desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ];\n\t\t\t\tvar min_size = this.resizing_node.computeSize();\n\t\t\t\tdesired_size[0] = Math.max( min_size[0], desired_size[0] );\n\t\t\t\tdesired_size[1] = Math.max( min_size[1], desired_size[1] );\n\t\t\t\tthis.resizing_node.setSize( desired_size );\n\n                this.canvas.style.cursor = \"se-resize\";\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n            }\n        }\n\n        e.preventDefault();\n        return false;\n    };\n\n    /**\n     * Called when a mouse up event has to be processed\n     * @method processMouseUp\n     **/\n    LGraphCanvas.prototype.processMouseUp = function(e) {\n\n\t\tvar is_primary = ( e.isPrimary === undefined || e.isPrimary );\n\n    \t//early exit for extra pointer\n    \tif(!is_primary){\n    \t\t/*e.stopPropagation();\n        \te.preventDefault();*/\n    \t\t//console.log(\"pointerevents: processMouseUp pointerN_stop \"+e.pointerId+\" \"+e.isPrimary);\n    \t\treturn false;\n    \t}\n    \t\n    \t//console.log(\"pointerevents: processMouseUp \"+e.pointerId+\" \"+e.isPrimary+\" :: \"+e.clientX+\" \"+e.clientY);\n    \t\n\t\tif( this.set_canvas_dirty_on_mouse_event )\n\t\t\tthis.dirty_canvas = true;\n\n        if (!this.graph)\n            return;\n\n        var window = this.getCanvasWindow();\n        var document = window.document;\n        LGraphCanvas.active_canvas = this;\n\n        //restore the mousemove event back to the canvas\n\t\tif(!this.options.skip_events)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseUp adjustEventListener\");\n\t\t\tLiteGraph.pointerListenerRemove(document,\"move\", this._mousemove_callback,true);\n\t\t\tLiteGraph.pointerListenerAdd(this.canvas,\"move\", this._mousemove_callback,true);\n\t\t\tLiteGraph.pointerListenerRemove(document,\"up\", this._mouseup_callback,true);\n\t\t}\n\n        this.adjustMouseEvent(e);\n        var now = LiteGraph.getTime();\n        e.click_time = now - this.last_mouseclick;\n        this.last_mouse_dragging = false;\n\t\tthis.last_click_position = null;\n\n\t\tif(this.block_click)\n\t\t{\n\t\t\t//console.log(\"pointerevents: processMouseUp block_clicks\");\n\t\t\tthis.block_click = false; //used to avoid sending twice a click in a immediate button\n\t\t}\n\n\t\t//console.log(\"pointerevents: processMouseUp which: \"+e.which);\n\t\t\n        if (e.which == 1) {\n\n\t\t\tif( this.node_widget )\n\t\t\t{\n\t\t\t\tthis.processNodeWidgets( this.node_widget[0], this.graph_mouse, e );\n\t\t\t}\n\n            //left button\n            this.node_widget = null;\n\n            if (this.selected_group) {\n                var diffx =\n                    this.selected_group.pos[0] -\n                    Math.round(this.selected_group.pos[0]);\n                var diffy =\n                    this.selected_group.pos[1] -\n                    Math.round(this.selected_group.pos[1]);\n                this.selected_group.move(diffx, diffy, e.ctrlKey);\n                this.selected_group.pos[0] = Math.round(\n                    this.selected_group.pos[0]\n                );\n                this.selected_group.pos[1] = Math.round(\n                    this.selected_group.pos[1]\n                );\n                if (this.selected_group._nodes.length) {\n                    this.dirty_canvas = true;\n                }\n                this.selected_group = null;\n            }\n            this.selected_group_resizing = false;\n\n\t\t\tvar node = this.graph.getNodeOnPos(\n\t\t\t\t\t\t\te.canvasX,\n\t\t\t\t\t\t\te.canvasY,\n\t\t\t\t\t\t\tthis.visible_nodes\n\t\t\t\t\t\t);\n\t\t\t\n            if (this.dragging_rectangle) {\n                if (this.graph) {\n                    var nodes = this.graph._nodes;\n                    var node_bounding = new Float32Array(4);\n                    \n                    //compute bounding and flip if left to right\n                    var w = Math.abs(this.dragging_rectangle[2]);\n                    var h = Math.abs(this.dragging_rectangle[3]);\n                    var startx =\n                        this.dragging_rectangle[2] < 0\n                            ? this.dragging_rectangle[0] - w\n                            : this.dragging_rectangle[0];\n                    var starty =\n                        this.dragging_rectangle[3] < 0\n                            ? this.dragging_rectangle[1] - h\n                            : this.dragging_rectangle[1];\n                    this.dragging_rectangle[0] = startx;\n                    this.dragging_rectangle[1] = starty;\n                    this.dragging_rectangle[2] = w;\n                    this.dragging_rectangle[3] = h;\n\n\t\t\t\t\t// test dragging rect size, if minimun simulate a click\n\t\t\t\t\tif (!node || (w > 10 && h > 10 )){\n\t\t\t\t\t\t//test against all nodes (not visible because the rectangle maybe start outside\n\t\t\t\t\t\tvar to_select = [];\n\t\t\t\t\t\tfor (var i = 0; i < nodes.length; ++i) {\n\t\t\t\t\t\t\tvar nodeX = nodes[i];\n\t\t\t\t\t\t\tnodeX.getBounding(node_bounding);\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!overlapBounding(\n\t\t\t\t\t\t\t\t\tthis.dragging_rectangle,\n\t\t\t\t\t\t\t\t\tnode_bounding\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t} //out of the visible area\n\t\t\t\t\t\t\tto_select.push(nodeX);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (to_select.length) {\n\t\t\t\t\t\t\tthis.selectNodes(to_select,e.shiftKey); // add to selection with shift\n\t\t\t\t\t\t}\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// will select of update selection\n\t\t\t\t\t\tthis.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey\n\t\t\t\t\t}\n\t\t\t\t\t\n                }\n                this.dragging_rectangle = null;\n            } else if (this.connecting_node) {\n                //dragging a connection\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n\n                var connInOrOut = this.connecting_output || this.connecting_input;\n                var connType = connInOrOut.type;\n                \n                //node below mouse\n                if (node) {\n                    \n                    /* no need to condition on event type.. just another type\n                    if (\n                        connType == LiteGraph.EVENT &&\n                        this.isOverNodeBox(node, e.canvasX, e.canvasY)\n                    ) {\n                        \n                        this.connecting_node.connect(\n                            this.connecting_slot,\n                            node,\n                            LiteGraph.EVENT\n                        );\n                        \n                    } else {*/\n                        \n                        //slot below mouse? connect\n                        \n                        if (this.connecting_output){\n                            \n                            var slot = this.isOverNodeInput(\n                                node,\n                                e.canvasX,\n                                e.canvasY\n                            );\n                            if (slot != -1) {\n                                this.connecting_node.connect(this.connecting_slot, node, slot);\n                            } else {\n                                //not on top of an input\n                                // look for a good slot\n                                this.connecting_node.connectByType(this.connecting_slot,node,connType);\n                            }\n                            \n                        }else if (this.connecting_input){\n                            \n                            var slot = this.isOverNodeOutput(\n                                node,\n                                e.canvasX,\n                                e.canvasY\n                            );\n\n                            if (slot != -1) {\n                                node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like\n                            } else {\n                                //not on top of an input\n                                // look for a good slot\n                                this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType);\n                            }\n                            \n                        }\n                        \n                        \n                    //}\n                    \n                }else{\n                    \n                    // add menu when releasing link in empty space\n                \tif (LiteGraph.release_link_on_empty_shows_menu){\n\t                    if (e.shiftKey && this.allow_searchbox){\n\t                        if(this.connecting_output){\n\t                            this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type});\n\t                        }else if(this.connecting_input){\n\t                            this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type});\n\t                        }\n\t                    }else{\n\t                        if(this.connecting_output){\n\t                            this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e});\n\t                        }else if(this.connecting_input){\n\t                            this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e});\n\t                        }\n\t                    }\n                \t}\n                }\n\n                this.connecting_output = null;\n                this.connecting_input = null;\n                this.connecting_pos = null;\n                this.connecting_node = null;\n                this.connecting_slot = -1;\n            } //not dragging connection\n            else if (this.resizing_node) {\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n\t\t\t\tthis.graph.afterChange(this.resizing_node);\n                this.resizing_node = null;\n            } else if (this.node_dragged) {\n                //node being dragged?\n                var node = this.node_dragged;\n                if (\n                    node &&\n                    e.click_time < 300 &&\n                    isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT )\n                ) {\n                    node.collapse();\n                }\n\n                this.dirty_canvas = true;\n                this.dirty_bgcanvas = true;\n                this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]);\n                this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]);\n                if (this.graph.config.align_to_grid || this.align_to_grid ) {\n                    this.node_dragged.alignToGrid();\n                }\n\t\t\t\tif( this.onNodeMoved )\n\t\t\t\t\tthis.onNodeMoved( this.node_dragged );\n\t\t\t\tthis.graph.afterChange(this.node_dragged);\n                this.node_dragged = null;\n            } //no node being dragged\n            else {\n                //get node over\n                var node = this.graph.getNodeOnPos(\n                    e.canvasX,\n                    e.canvasY,\n                    this.visible_nodes\n                );\n\n                if (!node && e.click_time < 300) {\n                    this.deselectAllNodes();\n                }\n\n                this.dirty_canvas = true;\n                this.dragging_canvas = false;\n\n                if (this.node_over && this.node_over.onMouseUp) {\n                    this.node_over.onMouseUp( e, [ e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1] ], this );\n                }\n                if (\n                    this.node_capturing_input &&\n                    this.node_capturing_input.onMouseUp\n                ) {\n                    this.node_capturing_input.onMouseUp(e, [\n                        e.canvasX - this.node_capturing_input.pos[0],\n                        e.canvasY - this.node_capturing_input.pos[1]\n                    ]);\n                }\n            }\n        } else if (e.which == 2) {\n            //middle button\n            //trace(\"middle\");\n            this.dirty_canvas = true;\n            this.dragging_canvas = false;\n        } else if (e.which == 3) {\n            //right button\n            //trace(\"right\");\n            this.dirty_canvas = true;\n            this.dragging_canvas = false;\n        }\n\n        /*\n\t\tif((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)\n\t\t\tthis.draw();\n\t\t*/\n\n\t  \tif (is_primary)\n\t\t{\n\t\t\tthis.pointer_is_down = false;\n\t\t\tthis.pointer_is_double = false;\n\t\t}\n\t  \n        this.graph.change();\n\n        //console.log(\"pointerevents: processMouseUp stopPropagation\");\n        e.stopPropagation();\n        e.preventDefault();\n        return false;\n    };\n\n    /**\n     * Called when a mouse wheel event has to be processed\n     * @method processMouseWheel\n     **/\n    LGraphCanvas.prototype.processMouseWheel = function(e) {\n        if (!this.graph || !this.allow_dragcanvas) {\n            return;\n        }\n\n        var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60;\n\n        this.adjustMouseEvent(e);\n\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\t\tif(!is_inside)\n\t\t\treturn;\n\n        var scale = this.ds.scale;\n\n        if (delta > 0) {\n            scale *= 1.1;\n        } else if (delta < 0) {\n            scale *= 1 / 1.1;\n        }\n\n        //this.setZoom( scale, [ e.clientX, e.clientY ] );\n        this.ds.changeScale(scale, [e.clientX, e.clientY]);\n\n        this.graph.change();\n\n        e.preventDefault();\n        return false; // prevent default\n    };\n\n    /**\n     * returns true if a position (in graph space) is on top of a node little corner box\n     * @method isOverNodeBox\n     **/\n    LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) {\n        var title_height = LiteGraph.NODE_TITLE_HEIGHT;\n        if (\n            isInsideRectangle(\n                canvasx,\n                canvasy,\n                node.pos[0] + 2,\n                node.pos[1] + 2 - title_height,\n                title_height - 4,\n                title_height - 4\n            )\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    /**\n     * returns the INDEX if a position (in graph space) is on top of a node input slot\n     * @method isOverNodeInput\n     **/\n    LGraphCanvas.prototype.isOverNodeInput = function(\n        node,\n        canvasx,\n        canvasy,\n        slot_pos\n    ) {\n        if (node.inputs) {\n            for (var i = 0, l = node.inputs.length; i < l; ++i) {\n                var input = node.inputs[i];\n                var link_pos = node.getConnectionPos(true, i);\n                var is_inside = false;\n                if (node.horizontal) {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 5,\n                        link_pos[1] - 10,\n                        10,\n                        20\n                    );\n                } else {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        40,\n                        10\n                    );\n                }\n                if (is_inside) {\n                    if (slot_pos) {\n                        slot_pos[0] = link_pos[0];\n                        slot_pos[1] = link_pos[1];\n                    }\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n    \n    /**\n     * returns the INDEX if a position (in graph space) is on top of a node output slot\n     * @method isOverNodeOuput\n     **/\n    LGraphCanvas.prototype.isOverNodeOutput = function(\n        node,\n        canvasx,\n        canvasy,\n        slot_pos\n    ) {\n        if (node.outputs) {\n            for (var i = 0, l = node.outputs.length; i < l; ++i) {\n                var output = node.outputs[i];\n                var link_pos = node.getConnectionPos(false, i);\n                var is_inside = false;\n                if (node.horizontal) {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 5,\n                        link_pos[1] - 10,\n                        10,\n                        20\n                    );\n                } else {\n                    is_inside = isInsideRectangle(\n                        canvasx,\n                        canvasy,\n                        link_pos[0] - 10,\n                        link_pos[1] - 5,\n                        40,\n                        10\n                    );\n                }\n                if (is_inside) {\n                    if (slot_pos) {\n                        slot_pos[0] = link_pos[0];\n                        slot_pos[1] = link_pos[1];\n                    }\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n\n    /**\n     * process a key event\n     * @method processKey\n     **/\n    LGraphCanvas.prototype.processKey = function(e) {\n        if (!this.graph) {\n            return;\n        }\n\n        var block_default = false;\n        //console.log(e); //debug\n\n        if (e.target.localName == \"input\") {\n            return;\n        }\n\n        if (e.type == \"keydown\") {\n            if (e.keyCode == 32) {\n                //space\n                this.dragging_canvas = true;\n                block_default = true;\n            }\n            \n            if (e.keyCode == 27) {\n                //esc\n                if(this.node_panel) this.node_panel.close();\n                if(this.options_panel) this.options_panel.close();\n                block_default = true;\n            }\n\n            //select all Control A\n            if (e.keyCode == 65 && e.ctrlKey) {\n                this.selectNodes();\n                block_default = true;\n            }\n\n            if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) {\n                //copy\n                if (this.selected_nodes) {\n                    this.copyToClipboard();\n                    block_default = true;\n                }\n            }\n\n            if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) {\n                //paste\n                this.pasteFromClipboard(e.shiftKey);\n            }\n\n            //delete or backspace\n            if (e.keyCode == 46 || e.keyCode == 8) {\n                if (\n                    e.target.localName != \"input\" &&\n                    e.target.localName != \"textarea\"\n                ) {\n                    this.deleteSelectedNodes();\n                    block_default = true;\n                }\n            }\n\n            //collapse\n            //...\n\n            //TODO\n            if (this.selected_nodes) {\n                for (var i in this.selected_nodes) {\n                    if (this.selected_nodes[i].onKeyDown) {\n                        this.selected_nodes[i].onKeyDown(e);\n                    }\n                }\n            }\n        } else if (e.type == \"keyup\") {\n            if (e.keyCode == 32) {\n                // space\n                this.dragging_canvas = false;\n            }\n\n            if (this.selected_nodes) {\n                for (var i in this.selected_nodes) {\n                    if (this.selected_nodes[i].onKeyUp) {\n                        this.selected_nodes[i].onKeyUp(e);\n                    }\n                }\n            }\n        }\n\n        this.graph.change();\n\n        if (block_default) {\n            e.preventDefault();\n            e.stopImmediatePropagation();\n            return false;\n        }\n    };\n\n    LGraphCanvas.prototype.copyToClipboard = function() {\n        var clipboard_info = {\n            nodes: [],\n            links: []\n        };\n        var index = 0;\n        var selected_nodes_array = [];\n        for (var i in this.selected_nodes) {\n            var node = this.selected_nodes[i];\n            if (node.clonable === false)\n                continue;\n            node._relative_id = index;\n            selected_nodes_array.push(node);\n            index += 1;\n        }\n\n        for (var i = 0; i < selected_nodes_array.length; ++i) {\n            var node = selected_nodes_array[i];\n            if(node.clonable === false)\n                continue;\n            var cloned = node.clone();\n            if(!cloned)\n            {\n                console.warn(\"node type not found: \" + node.type );\n                continue;\n            }\n            clipboard_info.nodes.push(cloned.serialize());\n            if (node.inputs && node.inputs.length) {\n                for (var j = 0; j < node.inputs.length; ++j) {\n                    var input = node.inputs[j];\n                    if (!input || input.link == null) {\n                        continue;\n                    }\n                    var link_info = this.graph.links[input.link];\n                    if (!link_info) {\n                        continue;\n                    }\n                    var target_node = this.graph.getNodeById(\n                        link_info.origin_id\n                    );\n                    if (!target_node) {\n                        continue;\n                    }\n                    clipboard_info.links.push([\n                        target_node._relative_id,\n                        link_info.origin_slot, //j,\n                        node._relative_id,\n                        link_info.target_slot,\n                        target_node.id\n                    ]);\n                }\n            }\n        }\n        localStorage.setItem(\n            \"litegrapheditor_clipboard\",\n            JSON.stringify(clipboard_info)\n        );\n    };\n\n    LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) {\n        // if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior\n        if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {\n            return;\n        }\n        var data = localStorage.getItem(\"litegrapheditor_clipboard\");\n        if (!data) {\n            return;\n        }\n\n\t\tthis.graph.beforeChange();\n\n        //create nodes\n        var clipboard_info = JSON.parse(data);\n        // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos\n        var posMin = false;\n        var posMinIndexes = false;\n        for (var i = 0; i < clipboard_info.nodes.length; ++i) {\n            if (posMin){\n                if(posMin[0]>clipboard_info.nodes[i].pos[0]){\n                    posMin[0] = clipboard_info.nodes[i].pos[0];\n                    posMinIndexes[0] = i;\n                }\n                if(posMin[1]>clipboard_info.nodes[i].pos[1]){\n                    posMin[1] = clipboard_info.nodes[i].pos[1];\n                    posMinIndexes[1] = i;\n                }\n            }\n            else{\n                posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]];\n                posMinIndexes = [i, i];\n            }\n        }\n        var nodes = [];\n        for (var i = 0; i < clipboard_info.nodes.length; ++i) {\n            var node_data = clipboard_info.nodes[i];\n            var node = LiteGraph.createNode(node_data.type);\n            if (node) {\n                node.configure(node_data);\n        \n\t\t\t\t//paste in last known mouse position\n                node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5;\n                node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5;\n\n                this.graph.add(node,{doProcessChange:false});\n                \n                nodes.push(node);\n            }\n        }\n\n        //create links\n        for (var i = 0; i < clipboard_info.links.length; ++i) {\n            var link_info = clipboard_info.links[i];\n            var origin_node;\n            var origin_node_relative_id = link_info[0];\n            if (origin_node_relative_id != null) {\n                origin_node = nodes[origin_node_relative_id];\n            } else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {\n                var origin_node_id = link_info[4];\n                if (origin_node_id) {\n                    origin_node = this.graph.getNodeById(origin_node_id);\n                }\n            }\n            var target_node = nodes[link_info[2]];\n\t\t\tif( origin_node && target_node )\n\t            origin_node.connect(link_info[1], target_node, link_info[3]);\n\t\t\telse\n\t\t\t\tconsole.warn(\"Warning, nodes missing on pasting\");\n        }\n\n        this.selectNodes(nodes);\n\n\t\tthis.graph.afterChange();\n    };\n\n    /**\n     * process a item drop event on top the canvas\n     * @method processDrop\n     **/\n    LGraphCanvas.prototype.processDrop = function(e) {\n        e.preventDefault();\n        this.adjustMouseEvent(e);\n\t\tvar x = e.clientX;\n\t\tvar y = e.clientY;\n\t\tvar is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) );\n\t\tif(!is_inside){\n\t\t\treturn;\n\t\t\t// --- BREAK ---\n\t\t}\n\n        var pos = [e.canvasX, e.canvasY];\n\n\n        var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null;\n\n        if (!node) {\n            var r = null;\n            if (this.onDropItem) {\n                r = this.onDropItem(event);\n            }\n            if (!r) {\n                this.checkDropItem(e);\n            }\n            return;\n        }\n\n        if (node.onDropFile || node.onDropData) {\n            var files = e.dataTransfer.files;\n            if (files && files.length) {\n                for (var i = 0; i < files.length; i++) {\n                    var file = e.dataTransfer.files[0];\n                    var filename = file.name;\n                    var ext = LGraphCanvas.getFileExtension(filename);\n                    //console.log(file);\n\n                    if (node.onDropFile) {\n                        node.onDropFile(file);\n                    }\n\n                    if (node.onDropData) {\n                        //prepare reader\n                        var reader = new FileReader();\n                        reader.onload = function(event) {\n                            //console.log(event.target);\n                            var data = event.target.result;\n                            node.onDropData(data, filename, file);\n                        };\n\n                        //read data\n                        var type = file.type.split(\"/\")[0];\n                        if (type == \"text\" || type == \"\") {\n                            reader.readAsText(file);\n                        } else if (type == \"image\") {\n                            reader.readAsDataURL(file);\n                        } else {\n                            reader.readAsArrayBuffer(file);\n                        }\n                    }\n                }\n            }\n        }\n\n        if (node.onDropItem) {\n            if (node.onDropItem(event)) {\n                return true;\n            }\n        }\n\n        if (this.onDropItem) {\n            return this.onDropItem(event);\n        }\n\n        return false;\n    };\n\n    //called if the graph doesn't have a default drop item behaviour\n    LGraphCanvas.prototype.checkDropItem = function(e) {\n        if (e.dataTransfer.files.length) {\n            var file = e.dataTransfer.files[0];\n            var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase();\n            var nodetype = LiteGraph.node_types_by_file_extension[ext];\n            if (nodetype) {\n\t\t\t\tthis.graph.beforeChange();\n                var node = LiteGraph.createNode(nodetype.type);\n                node.pos = [e.canvasX, e.canvasY];\n                this.graph.add(node);\n                if (node.onDropFile) {\n                    node.onDropFile(file);\n                }\n\t\t\t\tthis.graph.afterChange();\n            }\n        }\n    };\n\n    LGraphCanvas.prototype.processNodeDblClicked = function(n) {\n        if (this.onShowNodePanel) {\n            this.onShowNodePanel(n);\n        }\n\t\telse\n\t\t{\n\t\t\tthis.showShowNodePanel(n);\n\t\t}\n\n        if (this.onNodeDblClicked) {\n            this.onNodeDblClicked(n);\n        }\n\n        this.setDirty(true);\n    };\n\n    LGraphCanvas.prototype.processNodeSelected = function(node, e) {\n        this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select));\n        if (this.onNodeSelected) {\n            this.onNodeSelected(node);\n        }\n    };\n\n    /**\n     * selects a given node (or adds it to the current selection)\n     * @method selectNode\n     **/\n    LGraphCanvas.prototype.selectNode = function(\n        node,\n        add_to_current_selection\n    ) {\n        if (node == null) {\n            this.deselectAllNodes();\n        } else {\n            this.selectNodes([node], add_to_current_selection);\n        }\n    };\n\n    /**\n     * selects several nodes (or adds them to the current selection)\n     * @method selectNodes\n     **/\n    LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection )\n\t{\n\t\tif (!add_to_current_selection) {\n            this.deselectAllNodes();\n        }\n\n        nodes = nodes || this.graph._nodes;\n\t\tif (typeof nodes == \"string\") nodes = [nodes];\n        for (var i in nodes) {\n            var node = nodes[i];\n            if (node.is_selected) {\n                this.deselectNode(node);\n                continue;\n            }\n\n            if (!node.is_selected && node.onSelected) {\n                node.onSelected();\n            }\n            node.is_selected = true;\n            this.selected_nodes[node.id] = node;\n\n            if (node.inputs) {\n                for (var j = 0; j < node.inputs.length; ++j) {\n                    this.highlighted_links[node.inputs[j].link] = true;\n                }\n            }\n            if (node.outputs) {\n                for (var j = 0; j < node.outputs.length; ++j) {\n                    var out = node.outputs[j];\n                    if (out.links) {\n                        for (var k = 0; k < out.links.length; ++k) {\n                            this.highlighted_links[out.links[k]] = true;\n                        }\n                    }\n                }\n            }\n        }\n\n\t\tif(\tthis.onSelectionChange )\n\t\t\tthis.onSelectionChange( this.selected_nodes );\n\n        this.setDirty(true);\n    };\n\n    /**\n     * removes a node from the current selection\n     * @method deselectNode\n     **/\n    LGraphCanvas.prototype.deselectNode = function(node) {\n        if (!node.is_selected) {\n            return;\n        }\n        if (node.onDeselected) {\n            node.onDeselected();\n        }\n        node.is_selected = false;\n\n        if (this.onNodeDeselected) {\n            this.onNodeDeselected(node);\n        }\n\n        //remove highlighted\n        if (node.inputs) {\n            for (var i = 0; i < node.inputs.length; ++i) {\n                delete this.highlighted_links[node.inputs[i].link];\n            }\n        }\n        if (node.outputs) {\n            for (var i = 0; i < node.outputs.length; ++i) {\n                var out = node.outputs[i];\n                if (out.links) {\n                    for (var j = 0; j < out.links.length; ++j) {\n                        delete this.highlighted_links[out.links[j]];\n                    }\n                }\n            }\n        }\n    };\n\n    /**\n     * removes all nodes from the current selection\n     * @method deselectAllNodes\n     **/\n    LGraphCanvas.prototype.deselectAllNodes = function() {\n        if (!this.graph) {\n            return;\n        }\n        var nodes = this.graph._nodes;\n        for (var i = 0, l = nodes.length; i < l; ++i) {\n            var node = nodes[i];\n            if (!node.is_selected) {\n                continue;\n            }\n            if (node.onDeselected) {\n                node.onDeselected();\n            }\n            node.is_selected = false;\n\t\t\tif (this.onNodeDeselected) {\n\t\t\t\tthis.onNodeDeselected(node);\n\t\t\t}\n        }\n        this.selected_nodes = {};\n        this.current_node = null;\n        this.highlighted_links = {};\n\t\tif(\tthis.onSelectionChange )\n\t\t\tthis.onSelectionChange( this.selected_nodes );\n        this.setDirty(true);\n    };\n\n    /**\n     * deletes all nodes in the current selection from the graph\n     * @method deleteSelectedNodes\n     **/\n    LGraphCanvas.prototype.deleteSelectedNodes = function() {\n\n\t\tthis.graph.beforeChange();\n\n        for (var i in this.selected_nodes) {\n            var node = this.selected_nodes[i];\n\n\t\t\tif(node.block_delete)\n\t\t\t\tcontinue;\n\n\t\t\t//autoconnect when possible (very basic, only takes into account first input-output)\n\t\t\tif(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) \n\t\t\t{\n\t\t\t\tvar input_link = node.graph.links[ node.inputs[0].link ];\n\t\t\t\tvar output_link = node.graph.links[ node.outputs[0].links[0] ];\n\t\t\t\tvar input_node = node.getInputNode(0);\n\t\t\t\tvar output_node = node.getOutputNodes(0)[0];\n\t\t\t\tif(input_node && output_node)\n\t\t\t\t\tinput_node.connect( input_link.origin_slot, output_node, output_link.target_slot );\n\t\t\t}\n            this.graph.remove(node);\n\t\t\tif (this.onNodeDeselected) {\n\t\t\t\tthis.onNodeDeselected(node);\n\t\t\t}\n        }\n        this.selected_nodes = {};\n        this.current_node = null;\n        this.highlighted_links = {};\n        this.setDirty(true);\n\t\tthis.graph.afterChange();\n    };\n    \n    /**\n     * centers the camera on a given node\n     * @method centerOnNode\n     **/\n    LGraphCanvas.prototype.centerOnNode = function(node) {\n        this.ds.offset[0] =\n            -node.pos[0] -\n            node.size[0] * 0.5 +\n            (this.canvas.width * 0.5) / this.ds.scale;\n        this.ds.offset[1] =\n            -node.pos[1] -\n            node.size[1] * 0.5 +\n            (this.canvas.height * 0.5) / this.ds.scale;\n        this.setDirty(true, true);\n    };\n\n    /**\n     * adds some useful properties to a mouse event, like the position in graph coordinates\n     * @method adjustMouseEvent\n     **/\n    LGraphCanvas.prototype.adjustMouseEvent = function(e) {\n\tvar clientX_rel = 0;\n        var clientY_rel = 0;\n\t    \n    \tif (this.canvas) {\n            var b = this.canvas.getBoundingClientRect();\n            clientX_rel = e.clientX - b.left;\n            clientY_rel = e.clientY - b.top;\n        } else {\n        \tclientX_rel = e.clientX;\n        \tclientY_rel = e.clientY;\n        }\n    \t\n        // e.deltaX = clientX_rel - this.last_mouse_position[0];\n        // e.deltaY = clientY_rel- this.last_mouse_position[1];\n\n        this.last_mouse_position[0] = clientX_rel;\n        this.last_mouse_position[1] = clientY_rel;\n\n        e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0];\n        e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1];\n        \n        //console.log(\"pointerevents: adjustMouseEvent \"+e.clientX+\":\"+e.clientY+\" \"+clientX_rel+\":\"+clientY_rel+\" \"+e.canvasX+\":\"+e.canvasY);\n    };\n\n    /**\n     * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom\n     * @method setZoom\n     **/\n    LGraphCanvas.prototype.setZoom = function(value, zooming_center) {\n        this.ds.changeScale(value, zooming_center);\n        /*\n\tif(!zooming_center && this.canvas)\n\t\tzooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5];\n\n\tvar center = this.convertOffsetToCanvas( zooming_center );\n\n\tthis.ds.scale = value;\n\n\tif(this.scale > this.max_zoom)\n\t\tthis.scale = this.max_zoom;\n\telse if(this.scale < this.min_zoom)\n\t\tthis.scale = this.min_zoom;\n\n\tvar new_center = this.convertOffsetToCanvas( zooming_center );\n\tvar delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];\n\n\tthis.offset[0] += delta_offset[0];\n\tthis.offset[1] += delta_offset[1];\n\t*/\n\n        this.dirty_canvas = true;\n        this.dirty_bgcanvas = true;\n    };\n\n    /**\n     * converts a coordinate from graph coordinates to canvas2D coordinates\n     * @method convertOffsetToCanvas\n     **/\n    LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) {\n        return this.ds.convertOffsetToCanvas(pos, out);\n    };\n\n    /**\n     * converts a coordinate from Canvas2D coordinates to graph space\n     * @method convertCanvasToOffset\n     **/\n    LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) {\n        return this.ds.convertCanvasToOffset(pos, out);\n    };\n\n    //converts event coordinates from canvas2D to graph coordinates\n    LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) {\n        var rect = this.canvas.getBoundingClientRect();\n        return this.convertCanvasToOffset([\n            e.clientX - rect.left,\n            e.clientY - rect.top\n        ]);\n    };\n\n    /**\n     * brings a node to front (above all other nodes)\n     * @method bringToFront\n     **/\n    LGraphCanvas.prototype.bringToFront = function(node) {\n        var i = this.graph._nodes.indexOf(node);\n        if (i == -1) {\n            return;\n        }\n\n        this.graph._nodes.splice(i, 1);\n        this.graph._nodes.push(node);\n    };\n\n    /**\n     * sends a node to the back (below all other nodes)\n     * @method sendToBack\n     **/\n    LGraphCanvas.prototype.sendToBack = function(node) {\n        var i = this.graph._nodes.indexOf(node);\n        if (i == -1) {\n            return;\n        }\n\n        this.graph._nodes.splice(i, 1);\n        this.graph._nodes.unshift(node);\n    };\n\n    /* Interaction */\n\n    /* LGraphCanvas render */\n    var temp = new Float32Array(4);\n\n    /**\n     * checks which nodes are visible (inside the camera area)\n     * @method computeVisibleNodes\n     **/\n    LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) {\n        var visible_nodes = out || [];\n        visible_nodes.length = 0;\n        nodes = nodes || this.graph._nodes;\n        for (var i = 0, l = nodes.length; i < l; ++i) {\n            var n = nodes[i];\n\n            //skip rendering nodes in live mode\n            if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) {\n                continue;\n            }\n\n            if (!overlapBounding(this.visible_area, n.getBounding(temp, true))) {\n                continue;\n            } //out of the visible area\n\n            visible_nodes.push(n);\n        }\n        return visible_nodes;\n    };\n\n    /**\n     * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)\n     * @method draw\n     **/\n    LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) {\n        if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) {\n            return;\n        }\n\n        //fps counting\n        var now = LiteGraph.getTime();\n        this.render_time = (now - this.last_draw_time) * 0.001;\n        this.last_draw_time = now;\n\n        if (this.graph) {\n            this.ds.computeVisibleArea(this.viewport);\n        }\n\n        if (\n            this.dirty_bgcanvas ||\n            force_bgcanvas ||\n            this.always_render_background ||\n            (this.graph &&\n                this.graph._last_trigger_time &&\n                now - this.graph._last_trigger_time < 1000)\n        ) {\n            this.drawBackCanvas();\n        }\n\n        if (this.dirty_canvas || force_canvas) {\n            this.drawFrontCanvas();\n        }\n\n        this.fps = this.render_time ? 1.0 / this.render_time : 0;\n        this.frame += 1;\n    };\n\n    /**\n     * draws the front canvas (the one containing all the nodes)\n     * @method drawFrontCanvas\n     **/\n    LGraphCanvas.prototype.drawFrontCanvas = function() {\n        this.dirty_canvas = false;\n\n        if (!this.ctx) {\n            this.ctx = this.bgcanvas.getContext(\"2d\");\n        }\n        var ctx = this.ctx;\n        if (!ctx) {\n            //maybe is using webgl...\n            return;\n        }\n\n        var canvas = this.canvas;\n        if ( ctx.start2D && !this.viewport ) {\n            ctx.start2D();\n\t\t\tctx.restore();\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n        }\n\n        //clip dirty area if there is one, otherwise work in full canvas\n\t\tvar area = this.viewport || this.dirty_area;\n        if (area) {\n            ctx.save();\n            ctx.beginPath();\n            ctx.rect( area[0],area[1],area[2],area[3] );\n            ctx.clip();\n        }\n\n        //clear\n        //canvas.width = canvas.width;\n        if (this.clear_background) {\n\t\t\tif(area)\n\t            ctx.clearRect( area[0],area[1],area[2],area[3] );\n\t\t\telse\n\t            ctx.clearRect(0, 0, canvas.width, canvas.height);\n        }\n\n        //draw bg canvas\n        if (this.bgcanvas == this.canvas) {\n            this.drawBackCanvas();\n        } else {\n            ctx.drawImage( this.bgcanvas, 0, 0 );\n        }\n\n        //rendering\n        if (this.onRender) {\n            this.onRender(canvas, ctx);\n        }\n\n        //info widget\n        if (this.show_info) {\n            this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0 );\n        }\n\n        if (this.graph) {\n            //apply transformations\n            ctx.save();\n            this.ds.toCanvasContext(ctx);\n\n            //draw nodes\n            var drawn_nodes = 0;\n            var visible_nodes = this.computeVisibleNodes(\n                null,\n                this.visible_nodes\n            );\n\n            for (var i = 0; i < visible_nodes.length; ++i) {\n                var node = visible_nodes[i];\n\n                //transform coords system\n                ctx.save();\n                ctx.translate(node.pos[0], node.pos[1]);\n\n                //Draw\n                this.drawNode(node, ctx);\n                drawn_nodes += 1;\n\n                //Restore\n                ctx.restore();\n            }\n\n            //on top (debug)\n            if (this.render_execution_order) {\n                this.drawExecutionOrder(ctx);\n            }\n\n            //connections ontop?\n            if (this.graph.config.links_ontop) {\n                if (!this.live_mode) {\n                    this.drawConnections(ctx);\n                }\n            }\n\n            //current connection (the one being dragged by the mouse)\n            if (this.connecting_pos != null) {\n                ctx.lineWidth = this.connections_width;\n                var link_color = null;\n                \n                var connInOrOut = this.connecting_output || this.connecting_input;\n\n                var connType = connInOrOut.type;\n                var connDir = connInOrOut.dir;\n\t\t\t\tif(connDir == null)\n\t\t\t\t{\n\t\t\t\t\tif (this.connecting_output)\n\t\t\t\t\t\tconnDir = this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT;\n\t\t\t\t\telse\n\t\t\t\t\t\tconnDir = this.connecting_node.horizontal ? LiteGraph.UP : LiteGraph.LEFT;\n\t\t\t\t}\n                var connShape = connInOrOut.shape;\n                \n                switch (connType) {\n                    case LiteGraph.EVENT:\n                        link_color = LiteGraph.EVENT_LINK_COLOR;\n                        break;\n                    default:\n                        link_color = LiteGraph.CONNECTING_LINK_COLOR;\n                }\n\n                //the connection being dragged by the mouse\n                this.renderLink(\n                    ctx,\n                    this.connecting_pos,\n                    [this.graph_mouse[0], this.graph_mouse[1]],\n                    null,\n                    false,\n                    null,\n                    link_color,\n                    connDir,\n                    LiteGraph.CENTER\n                );\n\n                ctx.beginPath();\n                if (\n                    connType === LiteGraph.EVENT ||\n                    connShape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(\n                        this.connecting_pos[0] - 6 + 0.5,\n                        this.connecting_pos[1] - 5 + 0.5,\n                        14,\n                        10\n                    );\n\t                ctx.fill();\n\t\t\t\t\tctx.beginPath();\n                    ctx.rect(\n                        this.graph_mouse[0] - 6 + 0.5,\n                        this.graph_mouse[1] - 5 + 0.5,\n                        14,\n                        10\n                    );\n                } else if (connShape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5);\n                    ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5);\n                    ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5);\n                    ctx.closePath();\n                } \n                else {\n                    ctx.arc(\n                        this.connecting_pos[0],\n                        this.connecting_pos[1],\n                        4,\n                        0,\n                        Math.PI * 2\n                    );\n\t                ctx.fill();\n\t\t\t\t\tctx.beginPath();\n                    ctx.arc(\n                        this.graph_mouse[0],\n                        this.graph_mouse[1],\n                        4,\n                        0,\n                        Math.PI * 2\n                    );\n                }\n                ctx.fill();\n\n                ctx.fillStyle = \"#ffcc00\";\n                if (this._highlight_input) {\n                    ctx.beginPath();\n                    var shape = this._highlight_input_slot.shape;\n                    if (shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5);\n                        ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5);\n                        ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else {\n                        ctx.arc(\n                            this._highlight_input[0],\n                            this._highlight_input[1],\n                            6,\n                            0,\n                            Math.PI * 2\n                        );\n                    }\n                    ctx.fill();\n                }\n                if (this._highlight_output) {\n                    ctx.beginPath();\n                    if (shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5);\n                        ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5);\n                        ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else {\n                        ctx.arc(\n                            this._highlight_output[0],\n                            this._highlight_output[1],\n                            6,\n                            0,\n                            Math.PI * 2\n                        );\n                    }\n                    ctx.fill();\n                }\n            }\n\n\t\t\t//the selection rectangle\n            if (this.dragging_rectangle) {\n                ctx.strokeStyle = \"#FFF\";\n                ctx.strokeRect(\n                    this.dragging_rectangle[0],\n                    this.dragging_rectangle[1],\n                    this.dragging_rectangle[2],\n                    this.dragging_rectangle[3]\n                );\n            }\n\n\t\t\t//on top of link center\n\t\t\tif(this.over_link_center && this.render_link_tooltip)\n\t\t\t\tthis.drawLinkTooltip( ctx, this.over_link_center );\n\t\t\telse\n\t\t\t\tif(this.onDrawLinkTooltip) //to remove\n\t\t\t\t\tthis.onDrawLinkTooltip(ctx,null);\n\n\t\t\t//custom info\n            if (this.onDrawForeground) {\n                this.onDrawForeground(ctx, this.visible_rect);\n            }\n\n            ctx.restore();\n        }\n\n\t\t//draws panel in the corner \n\t\tif (this._graph_stack && this._graph_stack.length) {\n\t\t\tthis.drawSubgraphPanel( ctx );\n\t\t}\n\n\n        if (this.onDrawOverlay) {\n            this.onDrawOverlay(ctx);\n        }\n\n        if (area){\n            ctx.restore();\n        }\n\n        if (ctx.finish2D) {\n            //this is a function I use in webgl renderer\n            ctx.finish2D();\n        }\n    };\n\n    /**\n     * draws the panel in the corner that shows subgraph properties\n     * @method drawSubgraphPanel\n     **/\n    LGraphCanvas.prototype.drawSubgraphPanel = function (ctx) {\n        var subgraph = this.graph;\n        var subnode = subgraph._subgraph_node;\n        if (!subnode) {\n            console.warn(\"subgraph without subnode\");\n            return;\n        }\n        this.drawSubgraphPanelLeft(subgraph, subnode, ctx)\n        this.drawSubgraphPanelRight(subgraph, subnode, ctx)\n    }\n\n    LGraphCanvas.prototype.drawSubgraphPanelLeft = function (subgraph, subnode, ctx) {\n        var num = subnode.inputs ? subnode.inputs.length : 0;\n        var w = 200;\n        var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6);\n\n        ctx.fillStyle = \"#111\";\n        ctx.globalAlpha = 0.8;\n        ctx.beginPath();\n        ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]);\n        ctx.fill();\n        ctx.globalAlpha = 1;\n\n        ctx.fillStyle = \"#888\";\n        ctx.font = \"14px Arial\";\n        ctx.textAlign = \"left\";\n        ctx.fillText(\"Graph Inputs\", 20, 34);\n        // var pos = this.mouse;\n\n        if (this.drawButton(w - 20, 20, 20, 20, \"X\", \"#151515\")) {\n            this.closeSubgraph();\n            return;\n        }\n\n        var y = 50;\n        ctx.font = \"14px Arial\";\n        if (subnode.inputs)\n            for (var i = 0; i < subnode.inputs.length; ++i) {\n                var input = subnode.inputs[i];\n                if (input.not_subgraph_input)\n                    continue;\n\n                //input button clicked\n                if (this.drawButton(20, y + 2, w - 20, h - 2)) {\n                    var type = subnode.constructor.input_node_type || \"graph/input\";\n                    this.graph.beforeChange();\n                    var newnode = LiteGraph.createNode(type);\n                    if (newnode) {\n                        subgraph.add(newnode);\n                        this.block_click = false;\n                        this.last_click_position = null;\n                        this.selectNodes([newnode]);\n                        this.node_dragged = newnode;\n                        this.dragging_canvas = false;\n                        newnode.setProperty(\"name\", input.name);\n                        newnode.setProperty(\"type\", input.type);\n                        this.node_dragged.pos[0] = this.graph_mouse[0] - 5;\n                        this.node_dragged.pos[1] = this.graph_mouse[1] - 5;\n                        this.graph.afterChange();\n                    }\n                    else\n                        console.error(\"graph input node not found:\", type);\n                }\n                ctx.fillStyle = \"#9C9\";\n                ctx.beginPath();\n                ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI);\n                ctx.fill();\n                ctx.fillStyle = \"#AAA\";\n                ctx.fillText(input.name, 30, y + h * 0.75);\n                // var tw = ctx.measureText(input.name);\n                ctx.fillStyle = \"#777\";\n                ctx.fillText(input.type, 130, y + h * 0.75);\n                y += h;\n            }\n        //add + button\n        if (this.drawButton(20, y + 2, w - 20, h - 2, \"+\", \"#151515\", \"#222\")) {\n            this.showSubgraphPropertiesDialog(subnode);\n        }\n    }\n    LGraphCanvas.prototype.drawSubgraphPanelRight = function (subgraph, subnode, ctx) {\n        var num = subnode.outputs ? subnode.outputs.length : 0;\n        var canvas_w = this.bgcanvas.width\n        var w = 200;\n        var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6);\n\n        ctx.fillStyle = \"#111\";\n        ctx.globalAlpha = 0.8;\n        ctx.beginPath();\n        ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]);\n        ctx.fill();\n        ctx.globalAlpha = 1;\n\n        ctx.fillStyle = \"#888\";\n        ctx.font = \"14px Arial\";\n        ctx.textAlign = \"left\";\n        var title_text = \"Graph Outputs\"\n        var tw = ctx.measureText(title_text).width\n        ctx.fillText(title_text, (canvas_w - tw) - 20, 34);\n        // var pos = this.mouse;\n        if (this.drawButton(canvas_w - w, 20, 20, 20, \"X\", \"#151515\")) {\n            this.closeSubgraph();\n            return;\n        }\n\n        var y = 50;\n        ctx.font = \"14px Arial\";\n        if (subnode.outputs)\n            for (var i = 0; i < subnode.outputs.length; ++i) {\n                var output = subnode.outputs[i];\n                if (output.not_subgraph_input)\n                    continue;\n\n                //output button clicked\n                if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2)) {\n                    var type = subnode.constructor.output_node_type || \"graph/output\";\n                    this.graph.beforeChange();\n                    var newnode = LiteGraph.createNode(type);\n                    if (newnode) {\n                        subgraph.add(newnode);\n                        this.block_click = false;\n                        this.last_click_position = null;\n                        this.selectNodes([newnode]);\n                        this.node_dragged = newnode;\n                        this.dragging_canvas = false;\n                        newnode.setProperty(\"name\", output.name);\n                        newnode.setProperty(\"type\", output.type);\n                        this.node_dragged.pos[0] = this.graph_mouse[0] - 5;\n                        this.node_dragged.pos[1] = this.graph_mouse[1] - 5;\n                        this.graph.afterChange();\n                    }\n                    else\n                        console.error(\"graph input node not found:\", type);\n                }\n                ctx.fillStyle = \"#9C9\";\n                ctx.beginPath();\n                ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI);\n                ctx.fill();\n                ctx.fillStyle = \"#AAA\";\n                ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75);\n                // var tw = ctx.measureText(input.name);\n                ctx.fillStyle = \"#777\";\n                ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75);\n                y += h;\n            }\n        //add + button\n        if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2, \"+\", \"#151515\", \"#222\")) {\n            this.showSubgraphPropertiesDialogRight(subnode);\n        }\n    }\n\t//Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm\n\tLGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor )\n\t{\n\t\tvar ctx = this.ctx;\n\t\tbgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR;\n\t\thovercolor = hovercolor || \"#555\";\n\t\ttextcolor = textcolor || LiteGraph.NODE_TEXT_COLOR;\n\t\tvar pos = this.ds.convertOffsetToCanvas(this.graph_mouse);\n\t\tvar hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tpos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null;\n        if(pos) {\n            var rect = this.canvas.getBoundingClientRect();\n            pos[0] -= rect.left;\n            pos[1] -= rect.top;\n        }\n\t\tvar clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\n\t\tctx.fillStyle = hover ? hovercolor : bgcolor;\n\t\tif(clicked)\n\t\t\tctx.fillStyle = \"#AAA\";\n\t\tctx.beginPath();\n\t\tctx.roundRect(x,y,w,h,[4] );\n\t\tctx.fill();\n\n\t\tif(text != null)\n\t\t{\n\t\t\tif(text.constructor == String)\n\t\t\t{\n\t\t\t\tctx.fillStyle = textcolor;\n\t\t\t\tctx.textAlign = \"center\";\n\t\t\t\tctx.font = ((h * 0.65)|0) + \"px Arial\";\n\t\t\t\tctx.fillText( text, x + w * 0.5,y + h * 0.75 );\n\t\t\t\tctx.textAlign = \"left\";\n\t\t\t}\n\t\t}\n\n\t\tvar was_clicked = clicked && !this.block_click;\n\t\tif(clicked)\n\t\t\tthis.blockClick();\n\t\treturn was_clicked;\n\t}\n\n\tLGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click )\n\t{\n\t\tvar pos = this.mouse;\n\t\tvar hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tpos = this.last_click_position;\n\t\tvar clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );\n\t\tvar was_clicked = clicked && !this.block_click;\n\t\tif(clicked && hold_click)\n\t\t\tthis.blockClick();\n\t\treturn was_clicked;\n\t}\n\n    /**\n     * draws some useful stats in the corner of the canvas\n     * @method renderInfo\n     **/\n    LGraphCanvas.prototype.renderInfo = function(ctx, x, y) {\n        x = x || 10;\n        y = y || this.canvas.height - 80;\n\n        ctx.save();\n        ctx.translate(x, y);\n\n        ctx.font = \"10px Arial\";\n        ctx.fillStyle = \"#888\";\n\t\tctx.textAlign = \"left\";\n        if (this.graph) {\n            ctx.fillText( \"T: \" + this.graph.globaltime.toFixed(2) + \"s\", 5, 13 * 1 );\n            ctx.fillText(\"I: \" + this.graph.iteration, 5, 13 * 2 );\n            ctx.fillText(\"N: \" + this.graph._nodes.length + \" [\" + this.visible_nodes.length + \"]\", 5, 13 * 3 );\n            ctx.fillText(\"V: \" + this.graph._version, 5, 13 * 4);\n            ctx.fillText(\"FPS:\" + this.fps.toFixed(2), 5, 13 * 5);\n        } else {\n            ctx.fillText(\"No graph selected\", 5, 13 * 1);\n        }\n        ctx.restore();\n    };\n\n    /**\n     * draws the back canvas (the one containing the background and the connections)\n     * @method drawBackCanvas\n     **/\n    LGraphCanvas.prototype.drawBackCanvas = function() {\n        var canvas = this.bgcanvas;\n        if (\n            canvas.width != this.canvas.width ||\n            canvas.height != this.canvas.height\n        ) {\n            canvas.width = this.canvas.width;\n            canvas.height = this.canvas.height;\n        }\n\n        if (!this.bgctx) {\n            this.bgctx = this.bgcanvas.getContext(\"2d\");\n        }\n        var ctx = this.bgctx;\n        if (ctx.start) {\n            ctx.start();\n        }\n\n\t\tvar viewport = this.viewport || [0,0,ctx.canvas.width,ctx.canvas.height];\n\n        //clear\n        if (this.clear_background) {\n            ctx.clearRect( viewport[0], viewport[1], viewport[2], viewport[3] );\n        }\n\n\t\t//show subgraph stack header\n        if (this._graph_stack && this._graph_stack.length) {\n            ctx.save();\n            var parent_graph = this._graph_stack[this._graph_stack.length - 1];\n            var subgraph_node = this.graph._subgraph_node;\n            ctx.strokeStyle = subgraph_node.bgcolor;\n            ctx.lineWidth = 10;\n            ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2);\n            ctx.lineWidth = 1;\n            ctx.font = \"40px Arial\";\n            ctx.textAlign = \"center\";\n            ctx.fillStyle = subgraph_node.bgcolor || \"#AAA\";\n            var title = \"\";\n            for (var i = 1; i < this._graph_stack.length; ++i) {\n                title +=\n                    this._graph_stack[i]._subgraph_node.getTitle() + \" >> \";\n            }\n            ctx.fillText(\n                title + subgraph_node.getTitle(),\n                canvas.width * 0.5,\n                40\n            );\n            ctx.restore();\n        }\n\n        var bg_already_painted = false;\n        if (this.onRenderBackground) {\n            bg_already_painted = this.onRenderBackground(canvas, ctx);\n        }\n\n        //reset in case of error\n        if ( !this.viewport )\n\t\t{\n\t        ctx.restore();\n\t\t    ctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t}\n        this.visible_links.length = 0;\n\n        if (this.graph) {\n            //apply transformations\n            ctx.save();\n            this.ds.toCanvasContext(ctx);\n\n            //render BG\n            if ( this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color )\n            {\n                ctx.fillStyle = this.clear_background_color;\n                ctx.fillRect(\n                    this.visible_area[0],\n                    this.visible_area[1],\n                    this.visible_area[2],\n                    this.visible_area[3]\n                );\n            }\n\n            if (\n                this.background_image &&\n                this.ds.scale > 0.5 &&\n                !bg_already_painted\n            ) {\n                if (this.zoom_modify_alpha) {\n                    ctx.globalAlpha =\n                        (1.0 - 0.5 / this.ds.scale) * this.editor_alpha;\n                } else {\n                    ctx.globalAlpha = this.editor_alpha;\n                }\n                ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = \n                if (\n                    !this._bg_img ||\n                    this._bg_img.name != this.background_image\n                ) {\n                    this._bg_img = new Image();\n                    this._bg_img.name = this.background_image;\n                    this._bg_img.src = this.background_image;\n                    var that = this;\n                    this._bg_img.onload = function() {\n                        that.draw(true, true);\n                    };\n                }\n\n                var pattern = null;\n                if (this._pattern == null && this._bg_img.width > 0) {\n                    pattern = ctx.createPattern(this._bg_img, \"repeat\");\n                    this._pattern_img = this._bg_img;\n                    this._pattern = pattern;\n                } else {\n                    pattern = this._pattern;\n                }\n                if (pattern) {\n                    ctx.fillStyle = pattern;\n                    ctx.fillRect(\n                        this.visible_area[0],\n                        this.visible_area[1],\n                        this.visible_area[2],\n                        this.visible_area[3]\n                    );\n                    ctx.fillStyle = \"transparent\";\n                }\n\n                ctx.globalAlpha = 1.0;\n                ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled\n            }\n\n            //groups\n            if (this.graph._groups.length && !this.live_mode) {\n                this.drawGroups(canvas, ctx);\n            }\n\n            if (this.onDrawBackground) {\n                this.onDrawBackground(ctx, this.visible_area);\n            }\n            if (this.onBackgroundRender) {\n                //LEGACY\n                console.error(\n                    \"WARNING! onBackgroundRender deprecated, now is named onDrawBackground \"\n                );\n                this.onBackgroundRender = null;\n            }\n\n            //DEBUG: show clipping area\n            //ctx.fillStyle = \"red\";\n            //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20);\n\n            //bg\n            if (this.render_canvas_border) {\n                ctx.strokeStyle = \"#235\";\n                ctx.strokeRect(0, 0, canvas.width, canvas.height);\n            }\n\n            if (this.render_connections_shadows) {\n                ctx.shadowColor = \"#000\";\n                ctx.shadowOffsetX = 0;\n                ctx.shadowOffsetY = 0;\n                ctx.shadowBlur = 6;\n            } else {\n                ctx.shadowColor = \"rgba(0,0,0,0)\";\n            }\n\n            //draw connections\n            if (!this.live_mode) {\n                this.drawConnections(ctx);\n            }\n\n            ctx.shadowColor = \"rgba(0,0,0,0)\";\n\n            //restore state\n            ctx.restore();\n        }\n\n        if (ctx.finish) {\n            ctx.finish();\n        }\n\n        this.dirty_bgcanvas = false;\n        this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas\n    };\n\n    var temp_vec2 = new Float32Array(2);\n\n    /**\n     * draws the given node inside the canvas\n     * @method drawNode\n     **/\n    LGraphCanvas.prototype.drawNode = function(node, ctx) {\n        var glow = false;\n        this.current_node = node;\n\n        var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR;\n        var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;\n\n        //shadow and glow\n        if (node.mouseOver) {\n            glow = true;\n        }\n\n        var low_quality = this.ds.scale < 0.6; //zoomed out\n\n        //only render if it forces it to do it\n        if (this.live_mode) {\n            if (!node.flags.collapsed) {\n                ctx.shadowColor = \"transparent\";\n                if (node.onDrawForeground) {\n                    node.onDrawForeground(ctx, this, this.canvas);\n                }\n            }\n            return;\n        }\n\n        var editor_alpha = this.editor_alpha;\n        ctx.globalAlpha = editor_alpha;\n\n        if (this.render_shadows && !low_quality) {\n            ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;\n            ctx.shadowOffsetX = 2 * this.ds.scale;\n            ctx.shadowOffsetY = 2 * this.ds.scale;\n            ctx.shadowBlur = 3 * this.ds.scale;\n        } else {\n            ctx.shadowColor = \"transparent\";\n        }\n\n        //custom draw collapsed method (draw after shadows because they are affected)\n        if (\n            node.flags.collapsed &&\n            node.onDrawCollapsed &&\n            node.onDrawCollapsed(ctx, this) == true\n        ) {\n            return;\n        }\n\n        //clip if required (mask)\n        var shape = node._shape || LiteGraph.BOX_SHAPE;\n        var size = temp_vec2;\n        temp_vec2.set(node.size);\n        var horizontal = node.horizontal; // || node.flags.horizontal;\n\n        if (node.flags.collapsed) {\n            ctx.font = this.inner_text_font;\n            var title = node.getTitle ? node.getTitle() : node.title;\n            if (title != null) {\n                node._collapsed_width = Math.min(\n                    node.size[0],\n                    ctx.measureText(title).width +\n                        LiteGraph.NODE_TITLE_HEIGHT * 2\n                ); //LiteGraph.NODE_COLLAPSED_WIDTH;\n                size[0] = node._collapsed_width;\n                size[1] = 0;\n            }\n        }\n\n        if (node.clip_area) {\n            //Start clipping\n            ctx.save();\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE) {\n                ctx.rect(0, 0, size[0], size[1]);\n            } else if (shape == LiteGraph.ROUND_SHAPE) {\n                ctx.roundRect(0, 0, size[0], size[1], [10]);\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.clip();\n        }\n\n        //draw shape\n        if (node.has_errors) {\n            bgcolor = \"red\";\n        }\n        this.drawNodeShape(\n            node,\n            ctx,\n            size,\n            color,\n            bgcolor,\n            node.is_selected,\n            node.mouseOver\n        );\n        ctx.shadowColor = \"transparent\";\n\n        //draw foreground\n        if (node.onDrawForeground) {\n            node.onDrawForeground(ctx, this, this.canvas);\n        }\n\n        //connection slots\n        ctx.textAlign = horizontal ? \"center\" : \"left\";\n        ctx.font = this.inner_text_font;\n\n        var render_text = !low_quality;\n\n        var out_slot = this.connecting_output;\n        var in_slot = this.connecting_input;\n        ctx.lineWidth = 1;\n\n        var max_y = 0;\n        var slot_pos = new Float32Array(2); //to reuse\n\n        //render inputs and outputs\n        if (!node.flags.collapsed) {\n            //input connection slots\n            if (node.inputs) {\n                for (var i = 0; i < node.inputs.length; i++) {\n                    var slot = node.inputs[i];\n                    \n                    var slot_type = slot.type;\n                    var slot_shape = slot.shape;\n                    \n                    ctx.globalAlpha = editor_alpha;\n                    //change opacity of incompatible slots when dragging a connection\n                    if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) {\n                        ctx.globalAlpha = 0.4 * editor_alpha;\n                    }\n\n                    ctx.fillStyle =\n                        slot.link != null\n                            ? slot.color_on ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.input_on\n                            : slot.color_off ||\n                              this.default_connection_color_byTypeOff[slot_type] ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.input_off;\n\n                    var pos = node.getConnectionPos(true, i, slot_pos);\n                    pos[0] -= node.pos[0];\n                    pos[1] -= node.pos[1];\n                    if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) {\n                        max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n                    }\n\n                    ctx.beginPath();\n\n\t\t\t\t\tif (slot_type == \"array\"){\n                        slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead?\n                    }\n                    \n                    var doStroke = true;\n                    \n                    if (\n                        slot.type === LiteGraph.EVENT ||\n                        slot.shape === LiteGraph.BOX_SHAPE\n                    ) {\n                        if (horizontal) {\n                            ctx.rect(\n                                pos[0] - 5 + 0.5,\n                                pos[1] - 8 + 0.5,\n                                10,\n                                14\n                            );\n                        } else {\n                            ctx.rect(\n                                pos[0] - 6 + 0.5,\n                                pos[1] - 5 + 0.5,\n                                14,\n                                10\n                            );\n                        }\n                    } else if (slot_shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(pos[0] + 8, pos[1] + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);\n                        ctx.closePath();\n                    } else if (slot_shape === LiteGraph.GRID_SHAPE) {\n                        ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2);\n                        doStroke = false;\n                    } else {\n\t\t\t\t\t\tif(low_quality)\n\t                        ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster\n\t\t\t\t\t\telse\n\t                        ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);\n                    }\n                    ctx.fill();\n\n                    //render name\n                    if (render_text) {\n                        var text = slot.label != null ? slot.label : slot.name;\n                        if (text) {\n                            ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;\n                            if (horizontal || slot.dir == LiteGraph.UP) {\n                                ctx.fillText(text, pos[0], pos[1] - 10);\n                            } else {\n                                ctx.fillText(text, pos[0] + 10, pos[1] + 5);\n                            }\n                        }\n                    }\n                }\n            }\n\n            //output connection slots\n\n            ctx.textAlign = horizontal ? \"center\" : \"right\";\n            ctx.strokeStyle = \"black\";\n            if (node.outputs) {\n                for (var i = 0; i < node.outputs.length; i++) {\n                    var slot = node.outputs[i];\n                    \n                    var slot_type = slot.type;\n                    var slot_shape = slot.shape;\n                    \n                    //change opacity of incompatible slots when dragging a connection\n                    if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) {\n                        ctx.globalAlpha = 0.4 * editor_alpha;\n                    }\n                    \n                    var pos = node.getConnectionPos(false, i, slot_pos);\n                    pos[0] -= node.pos[0];\n                    pos[1] -= node.pos[1];\n                    if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) {\n                        max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5;\n                    }\n\n                    ctx.fillStyle =\n                        slot.links && slot.links.length\n                            ? slot.color_on ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.output_on\n                            : slot.color_off ||\n                              this.default_connection_color_byTypeOff[slot_type] ||\n                              this.default_connection_color_byType[slot_type] ||\n                              this.default_connection_color.output_off;\n                    ctx.beginPath();\n                    //ctx.rect( node.size[0] - 14,i*14,10,10);\n\n\t\t\t\t\tif (slot_type == \"array\"){\n                        slot_shape = LiteGraph.GRID_SHAPE;\n                    }\n                    \n                    var doStroke = true;\n                    \n                    if (\n                        slot_type === LiteGraph.EVENT ||\n                        slot_shape === LiteGraph.BOX_SHAPE\n                    ) {\n                        if (horizontal) {\n                            ctx.rect(\n                                pos[0] - 5 + 0.5,\n                                pos[1] - 8 + 0.5,\n                                10,\n                                14\n                            );\n                        } else {\n                            ctx.rect(\n                                pos[0] - 6 + 0.5,\n                                pos[1] - 5 + 0.5,\n                                14,\n                                10\n                            );\n                        }\n                    } else if (slot_shape === LiteGraph.ARROW_SHAPE) {\n                        ctx.moveTo(pos[0] + 8, pos[1] + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);\n                        ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);\n                        ctx.closePath();\n                    }  else if (slot_shape === LiteGraph.GRID_SHAPE) {\n                        ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2);\n                        ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2);\n                        ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2);\n                        doStroke = false;\n                    } else {\n\t\t\t\t\t\tif(low_quality)\n\t                        ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 );\n\t\t\t\t\t\telse\n\t                        ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);\n                    }\n\n                    //trigger\n                    //if(slot.node_id != null && slot.slot == -1)\n                    //\tctx.fillStyle = \"#F85\";\n\n                    //if(slot.links != null && slot.links.length)\n                    ctx.fill();\n\t\t\t\t\tif(!low_quality && doStroke)\n\t                    ctx.stroke();\n\n                    //render output name\n                    if (render_text) {\n                        var text = slot.label != null ? slot.label : slot.name;\n                        if (text) {\n                            ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;\n                            if (horizontal || slot.dir == LiteGraph.DOWN) {\n                                ctx.fillText(text, pos[0], pos[1] - 8);\n                            } else {\n                                ctx.fillText(text, pos[0] - 10, pos[1] + 5);\n                            }\n                        }\n                    }\n                }\n            }\n\n            ctx.textAlign = \"left\";\n            ctx.globalAlpha = 1;\n\n            if (node.widgets) {\n\t\t\t\tvar widgets_y = max_y;\n                if (horizontal || node.widgets_up) {\n                    widgets_y = 2;\n                }\n\t\t\t\tif( node.widgets_start_y != null )\n                    widgets_y = node.widgets_start_y;\n                this.drawNodeWidgets(\n                    node,\n                    widgets_y,\n                    ctx,\n                    this.node_widget && this.node_widget[0] == node\n                        ? this.node_widget[1]\n                        : null\n                );\n            }\n        } else if (this.render_collapsed_slots) {\n            //if collapsed\n            var input_slot = null;\n            var output_slot = null;\n\n            //get first connected slot to render\n            if (node.inputs) {\n                for (var i = 0; i < node.inputs.length; i++) {\n                    var slot = node.inputs[i];\n                    if (slot.link == null) {\n                        continue;\n                    }\n                    input_slot = slot;\n                    break;\n                }\n            }\n            if (node.outputs) {\n                for (var i = 0; i < node.outputs.length; i++) {\n                    var slot = node.outputs[i];\n                    if (!slot.links || !slot.links.length) {\n                        continue;\n                    }\n                    output_slot = slot;\n                }\n            }\n\n            if (input_slot) {\n                var x = 0;\n                var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center\n                if (horizontal) {\n                    x = node._collapsed_width * 0.5;\n                    y = -LiteGraph.NODE_TITLE_HEIGHT;\n                }\n                ctx.fillStyle = \"#686\";\n                ctx.beginPath();\n                if (\n                    slot.type === LiteGraph.EVENT ||\n                    slot.shape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(x - 7 + 0.5, y - 4, 14, 8);\n                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(x + 8, y);\n                    ctx.lineTo(x + -4, y - 4);\n                    ctx.lineTo(x + -4, y + 4);\n                    ctx.closePath();\n                } else {\n                    ctx.arc(x, y, 4, 0, Math.PI * 2);\n                }\n                ctx.fill();\n            }\n\n            if (output_slot) {\n                var x = node._collapsed_width;\n                var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center\n                if (horizontal) {\n                    x = node._collapsed_width * 0.5;\n                    y = 0;\n                }\n                ctx.fillStyle = \"#686\";\n                ctx.strokeStyle = \"black\";\n                ctx.beginPath();\n                if (\n                    slot.type === LiteGraph.EVENT ||\n                    slot.shape === LiteGraph.BOX_SHAPE\n                ) {\n                    ctx.rect(x - 7 + 0.5, y - 4, 14, 8);\n                } else if (slot.shape === LiteGraph.ARROW_SHAPE) {\n                    ctx.moveTo(x + 6, y);\n                    ctx.lineTo(x - 6, y - 4);\n                    ctx.lineTo(x - 6, y + 4);\n                    ctx.closePath();\n                } else {\n                    ctx.arc(x, y, 4, 0, Math.PI * 2);\n                }\n                ctx.fill();\n                //ctx.stroke();\n            }\n        }\n\n        if (node.clip_area) {\n            ctx.restore();\n        }\n\n        ctx.globalAlpha = 1.0;\n    };\n\n\t//used by this.over_link_center\n\tLGraphCanvas.prototype.drawLinkTooltip = function( ctx, link )\n\t{\n\t\tvar pos = link._pos;\n\t\tctx.fillStyle = \"black\";\n\t\tctx.beginPath();\n\t\tctx.arc( pos[0], pos[1], 3, 0, Math.PI * 2 );\n\t\tctx.fill();\n\n\t\tif(link.data == null)\n\t\t\treturn;\n\n\t\tif(this.onDrawLinkTooltip)\n\t\t\tif( this.onDrawLinkTooltip(ctx,link,this) == true )\n\t\t\t\treturn;\n\n\t\tvar data = link.data;\n\t\tvar text = null;\n\n\t\tif( data.constructor === Number )\n\t\t\ttext = data.toFixed(2);\n\t\telse if( data.constructor === String )\n\t\t\ttext = \"\\\"\" + data + \"\\\"\";\n\t\telse if( data.constructor === Boolean )\n\t\t\ttext = String(data);\n\t\telse if (data.toToolTip)\n\t\t\ttext = data.toToolTip();\n\t\telse\n\t\t\ttext = \"[\" + data.constructor.name + \"]\";\n\n\t\tif(text == null)\n\t\t\treturn;\n\t\ttext = text.substr(0,30); //avoid weird\n\n\t\tctx.font = \"14px Courier New\";\n\t\tvar info = ctx.measureText(text);\n\t\tvar w = info.width + 20;\n\t\tvar h = 24;\n\t\tctx.shadowColor = \"black\";\n\t\tctx.shadowOffsetX = 2;\n\t\tctx.shadowOffsetY = 2;\n\t\tctx.shadowBlur = 3;\n\t\tctx.fillStyle = \"#454\";\n\t\tctx.beginPath();\n\t\tctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]);\n\t\tctx.moveTo( pos[0] - 10, pos[1] - 15 );\n\t\tctx.lineTo( pos[0] + 10, pos[1] - 15 );\n\t\tctx.lineTo( pos[0], pos[1] - 5 );\n\t\tctx.fill();\n        ctx.shadowColor = \"transparent\";\n\t\tctx.textAlign = \"center\";\n\t\tctx.fillStyle = \"#CEC\";\n\t\tctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3);\n\t}\n\n    /**\n     * draws the shape of the given node in the canvas\n     * @method drawNodeShape\n     **/\n    var tmp_area = new Float32Array(4);\n\n    LGraphCanvas.prototype.drawNodeShape = function(\n        node,\n        ctx,\n        size,\n        fgcolor,\n        bgcolor,\n        selected,\n        mouse_over\n    ) {\n        //bg rect\n        ctx.strokeStyle = fgcolor;\n        ctx.fillStyle = bgcolor;\n\n        var title_height = LiteGraph.NODE_TITLE_HEIGHT;\n        var low_quality = this.ds.scale < 0.5;\n\n        //render node area depending on shape\n        var shape =\n            node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;\n\n        var title_mode = node.constructor.title_mode;\n\n        var render_title = true;\n        if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) {\n            render_title = false;\n        } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) {\n            render_title = true;\n        }\n\n        var area = tmp_area;\n        area[0] = 0; //x\n        area[1] = render_title ? -title_height : 0; //y\n        area[2] = size[0] + 1; //w\n        area[3] = render_title ? size[1] + title_height : size[1]; //h\n\n        var old_alpha = ctx.globalAlpha;\n\n        //full node shape\n        //if(node.flags.collapsed)\n        {\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE || low_quality) {\n                ctx.fillRect(area[0], area[1], area[2], area[3]);\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                shape == LiteGraph.CARD_SHAPE\n            ) {\n                ctx.roundRect(\n                    area[0],\n                    area[1],\n                    area[2],\n                    area[3],\n                    shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] \n                );\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.fill();\n\n\t\t\t//separator\n\t\t\tif(!node.flags.collapsed && render_title)\n\t\t\t{\n\t\t\t\tctx.shadowColor = \"transparent\";\n\t\t\t\tctx.fillStyle = \"rgba(0,0,0,0.2)\";\n\t\t\t\tctx.fillRect(0, -1, area[2], 2);\n\t\t\t}\n        }\n        ctx.shadowColor = \"transparent\";\n\n        if (node.onDrawBackground) {\n            node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse );\n        }\n\n        //title bg (remember, it is rendered ABOVE the node)\n        if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) {\n            //title bar\n            if (node.onDrawTitleBar) {\n                node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor );\n            } else if (\n                title_mode != LiteGraph.TRANSPARENT_TITLE &&\n                (node.constructor.title_color || this.render_title_colored)\n            ) {\n                var title_color = node.constructor.title_color || fgcolor;\n\n                if (node.flags.collapsed) {\n                    ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;\n                }\n\n                //* gradient test\n                if (this.use_gradients) {\n                    var grad = LGraphCanvas.gradients[title_color];\n                    if (!grad) {\n                        grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0);\n                        grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException\n                        grad.addColorStop(1, \"#000\");\n                    }\n                    ctx.fillStyle = grad;\n                } else {\n                    ctx.fillStyle = title_color;\n                }\n\n                //ctx.globalAlpha = 0.5 * old_alpha;\n                ctx.beginPath();\n                if (shape == LiteGraph.BOX_SHAPE || low_quality) {\n                    ctx.rect(0, -title_height, size[0] + 1, title_height);\n                } else if (  shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) {\n                    ctx.roundRect(\n                        0,\n                        -title_height,\n                        size[0] + 1,\n                        title_height,\n                        node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0]\n                    );\n                }\n                ctx.fill();\n                ctx.shadowColor = \"transparent\";\n            }\n\n            var colState = false;\n            if (LiteGraph.node_box_coloured_by_mode){\n                if(LiteGraph.NODE_MODES_COLORS[node.mode]){\n                    colState = LiteGraph.NODE_MODES_COLORS[node.mode];\n                }\n            }\n            if (LiteGraph.node_box_coloured_when_on){\n                colState = node.action_triggered ? \"#FFF\" : (node.execute_triggered ? \"#AAA\" : colState);\n            }\n            \n            //title box\n            var box_size = 10;\n            if (node.onDrawTitleBox) {\n                node.onDrawTitleBox(ctx, title_height, size, this.ds.scale);\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                shape == LiteGraph.CIRCLE_SHAPE ||\n                shape == LiteGraph.CARD_SHAPE\n            ) {\n                if (low_quality) {\n                    ctx.fillStyle = \"black\";\n                    ctx.beginPath();\n                    ctx.arc(\n                        title_height * 0.5,\n                        title_height * -0.5,\n                        box_size * 0.5 + 1,\n                        0,\n                        Math.PI * 2\n                    );\n                    ctx.fill();\n                }\n                \n                ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR;\n\t\t\t\tif(low_quality)\n\t\t\t\t\tctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size  );\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.arc(\n\t\t\t\t\t\ttitle_height * 0.5,\n\t\t\t\t\t\ttitle_height * -0.5,\n\t\t\t\t\t\tbox_size * 0.5,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tMath.PI * 2\n\t\t\t\t\t);\n\t\t\t\t\tctx.fill();\n\t\t\t\t}\n            } else {\n                if (low_quality) {\n                    ctx.fillStyle = \"black\";\n                    ctx.fillRect(\n                        (title_height - box_size) * 0.5 - 1,\n                        (title_height + box_size) * -0.5 - 1,\n                        box_size + 2,\n                        box_size + 2\n                    );\n                }\n                ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR;\n                ctx.fillRect(\n                    (title_height - box_size) * 0.5,\n                    (title_height + box_size) * -0.5,\n                    box_size,\n                    box_size\n                );\n            }\n            ctx.globalAlpha = old_alpha;\n\n            //title text\n            if (node.onDrawTitleText) {\n                node.onDrawTitleText(\n                    ctx,\n                    title_height,\n                    size,\n                    this.ds.scale,\n                    this.title_text_font,\n                    selected\n                );\n            }\n            if (!low_quality) {\n                ctx.font = this.title_text_font;\n                var title = String(node.getTitle());\n                if (title) {\n                    if (selected) {\n                        ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR;\n                    } else {\n                        ctx.fillStyle =\n                            node.constructor.title_text_color ||\n                            this.node_title_color;\n                    }\n                    if (node.flags.collapsed) {\n                        ctx.textAlign = \"left\";\n                        var measure = ctx.measureText(title);\n                        ctx.fillText(\n                            title.substr(0,20), //avoid urls too long\n                            title_height,// + measure.width * 0.5,\n                            LiteGraph.NODE_TITLE_TEXT_Y - title_height\n                        );\n                        ctx.textAlign = \"left\";\n                    } else {\n                        ctx.textAlign = \"left\";\n                        ctx.fillText(\n                            title,\n                            title_height,\n                            LiteGraph.NODE_TITLE_TEXT_Y - title_height\n                        );\n                    }\n                }\n            }\n\n\t\t\t//subgraph box\n\t\t\tif (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) {\n\t\t\t\tvar w = LiteGraph.NODE_TITLE_HEIGHT;\n\t\t\t\tvar x = node.size[0] - w;\n\t\t\t\tvar over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 );\n\t\t\t\tctx.fillStyle = over ? \"#888\" : \"#555\";\n\t\t\t\tif( shape == LiteGraph.BOX_SHAPE || low_quality)\n\t\t\t\t\tctx.fillRect(x+2, -w+2, w-4, w-4);\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.roundRect(x+2, -w+2, w-4, w-4,[4]);\n\t\t\t\t\tctx.fill();\n\t\t\t\t}\n\t\t\t\tctx.fillStyle = \"#333\";\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.moveTo(x + w * 0.2, -w * 0.6);\n\t\t\t\tctx.lineTo(x + w * 0.8, -w * 0.6);\n\t\t\t\tctx.lineTo(x + w * 0.5, -w * 0.3);\n\t\t\t\tctx.fill();\n\t\t\t}\n\n\t\t\t//custom title render\n            if (node.onDrawTitle) {\n                node.onDrawTitle(ctx);\n            }\n        }\n\n        //render selection marker\n        if (selected) {\n            if (node.onBounding) {\n                node.onBounding(area);\n            }\n\n            if (title_mode == LiteGraph.TRANSPARENT_TITLE) {\n                area[1] -= title_height;\n                area[3] += title_height;\n            }\n            ctx.lineWidth = 1;\n            ctx.globalAlpha = 0.8;\n            ctx.beginPath();\n            if (shape == LiteGraph.BOX_SHAPE) {\n                ctx.rect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3]\n                );\n            } else if (\n                shape == LiteGraph.ROUND_SHAPE ||\n                (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed)\n            ) {\n                ctx.roundRect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3],\n                    [this.round_radius * 2]\n                );\n            } else if (shape == LiteGraph.CARD_SHAPE) {\n                ctx.roundRect(\n                    -6 + area[0],\n                    -6 + area[1],\n                    12 + area[2],\n                    12 + area[3],\n                    [this.round_radius * 2,2,this.round_radius * 2,2]\n                );\n            } else if (shape == LiteGraph.CIRCLE_SHAPE) {\n                ctx.arc(\n                    size[0] * 0.5,\n                    size[1] * 0.5,\n                    size[0] * 0.5 + 6,\n                    0,\n                    Math.PI * 2\n                );\n            }\n            ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR;\n            ctx.stroke();\n            ctx.strokeStyle = fgcolor;\n            ctx.globalAlpha = 1;\n        }\n        \n        // these counter helps in conditioning drawing based on if the node has been executed or an action occurred\n        if (node.execute_triggered>0) node.execute_triggered--;\n        if (node.action_triggered>0) node.action_triggered--;\n    };\n\n    var margin_area = new Float32Array(4);\n    var link_bounding = new Float32Array(4);\n    var tempA = new Float32Array(2);\n    var tempB = new Float32Array(2);\n\n    /**\n     * draws every connection visible in the canvas\n     * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time\n     * @method drawConnections\n     **/\n    LGraphCanvas.prototype.drawConnections = function(ctx) {\n        var now = LiteGraph.getTime();\n        var visible_area = this.visible_area;\n        margin_area[0] = visible_area[0] - 20;\n        margin_area[1] = visible_area[1] - 20;\n        margin_area[2] = visible_area[2] + 40;\n        margin_area[3] = visible_area[3] + 40;\n\n        //draw connections\n        ctx.lineWidth = this.connections_width;\n\n        ctx.fillStyle = \"#AAA\";\n        ctx.strokeStyle = \"#AAA\";\n        ctx.globalAlpha = this.editor_alpha;\n        //for every node\n        var nodes = this.graph._nodes;\n        for (var n = 0, l = nodes.length; n < l; ++n) {\n            var node = nodes[n];\n            //for every input (we render just inputs because it is easier as every slot can only have one input)\n            if (!node.inputs || !node.inputs.length) {\n                continue;\n            }\n\n            for (var i = 0; i < node.inputs.length; ++i) {\n                var input = node.inputs[i];\n                if (!input || input.link == null) {\n                    continue;\n                }\n                var link_id = input.link;\n                var link = this.graph.links[link_id];\n                if (!link) {\n                    continue;\n                }\n\n                //find link info\n                var start_node = this.graph.getNodeById(link.origin_id);\n                if (start_node == null) {\n                    continue;\n                }\n                var start_node_slot = link.origin_slot;\n                var start_node_slotpos = null;\n                if (start_node_slot == -1) {\n                    start_node_slotpos = [\n                        start_node.pos[0] + 10,\n                        start_node.pos[1] + 10\n                    ];\n                } else {\n                    start_node_slotpos = start_node.getConnectionPos(\n                        false,\n                        start_node_slot,\n                        tempA\n                    );\n                }\n                var end_node_slotpos = node.getConnectionPos(true, i, tempB);\n\n                //compute link bounding\n                link_bounding[0] = start_node_slotpos[0];\n                link_bounding[1] = start_node_slotpos[1];\n                link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0];\n                link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1];\n                if (link_bounding[2] < 0) {\n                    link_bounding[0] += link_bounding[2];\n                    link_bounding[2] = Math.abs(link_bounding[2]);\n                }\n                if (link_bounding[3] < 0) {\n                    link_bounding[1] += link_bounding[3];\n                    link_bounding[3] = Math.abs(link_bounding[3]);\n                }\n\n                //skip links outside of the visible area of the canvas\n                if (!overlapBounding(link_bounding, margin_area)) {\n                    continue;\n                }\n\n                var start_slot = start_node.outputs[start_node_slot];\n                var end_slot = node.inputs[i];\n                if (!start_slot || !end_slot) {\n                    continue;\n                }\n                var start_dir =\n                    start_slot.dir ||\n                    (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT);\n                var end_dir =\n                    end_slot.dir ||\n                    (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT);\n\n                this.renderLink(\n                    ctx,\n                    start_node_slotpos,\n                    end_node_slotpos,\n                    link,\n                    false,\n                    0,\n                    null,\n                    start_dir,\n                    end_dir\n                );\n\n                //event triggered rendered on top\n                if (link && link._last_time && now - link._last_time < 1000) {\n                    var f = 2.0 - (now - link._last_time) * 0.002;\n                    var tmp = ctx.globalAlpha;\n                    ctx.globalAlpha = tmp * f;\n                    this.renderLink(\n                        ctx,\n                        start_node_slotpos,\n                        end_node_slotpos,\n                        link,\n                        true,\n                        f,\n                        \"white\",\n                        start_dir,\n                        end_dir\n                    );\n                    ctx.globalAlpha = tmp;\n                }\n            }\n        }\n        ctx.globalAlpha = 1;\n    };\n\n    /**\n     * draws a link between two points\n     * @method renderLink\n     * @param {vec2} a start pos\n     * @param {vec2} b end pos\n     * @param {Object} link the link object with all the link info\n     * @param {boolean} skip_border ignore the shadow of the link\n     * @param {boolean} flow show flow animation (for events)\n     * @param {string} color the color for the link\n     * @param {number} start_dir the direction enum\n     * @param {number} end_dir the direction enum\n     * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb)\n     **/\n    LGraphCanvas.prototype.renderLink = function(\n        ctx,\n        a,\n        b,\n        link,\n        skip_border,\n        flow,\n        color,\n        start_dir,\n        end_dir,\n        num_sublines\n    ) {\n        if (link) {\n            this.visible_links.push(link);\n        }\n\n        //choose color\n        if (!color && link) {\n            color = link.color || LGraphCanvas.link_type_colors[link.type];\n        }\n        if (!color) {\n            color = this.default_link_color;\n        }\n        if (link != null && this.highlighted_links[link.id]) {\n            color = \"#FFF\";\n        }\n\n        start_dir = start_dir || LiteGraph.RIGHT;\n        end_dir = end_dir || LiteGraph.LEFT;\n\n        var dist = distance(a, b);\n\n        if (this.render_connections_border && this.ds.scale > 0.6) {\n            ctx.lineWidth = this.connections_width + 4;\n        }\n        ctx.lineJoin = \"round\";\n        num_sublines = num_sublines || 1;\n        if (num_sublines > 1) {\n            ctx.lineWidth = 0.5;\n        }\n\n        //begin line shape\n        ctx.beginPath();\n        for (var i = 0; i < num_sublines; i += 1) {\n            var offsety = (i - (num_sublines - 1) * 0.5) * 5;\n\n            if (this.links_render_mode == LiteGraph.SPLINE_LINK) {\n                ctx.moveTo(a[0], a[1] + offsety);\n                var start_offset_x = 0;\n                var start_offset_y = 0;\n                var end_offset_x = 0;\n                var end_offset_y = 0;\n                switch (start_dir) {\n                    case LiteGraph.LEFT:\n                        start_offset_x = dist * -0.25;\n                        break;\n                    case LiteGraph.RIGHT:\n                        start_offset_x = dist * 0.25;\n                        break;\n                    case LiteGraph.UP:\n                        start_offset_y = dist * -0.25;\n                        break;\n                    case LiteGraph.DOWN:\n                        start_offset_y = dist * 0.25;\n                        break;\n                }\n                switch (end_dir) {\n                    case LiteGraph.LEFT:\n                        end_offset_x = dist * -0.25;\n                        break;\n                    case LiteGraph.RIGHT:\n                        end_offset_x = dist * 0.25;\n                        break;\n                    case LiteGraph.UP:\n                        end_offset_y = dist * -0.25;\n                        break;\n                    case LiteGraph.DOWN:\n                        end_offset_y = dist * 0.25;\n                        break;\n                }\n                ctx.bezierCurveTo(\n                    a[0] + start_offset_x,\n                    a[1] + start_offset_y + offsety,\n                    b[0] + end_offset_x,\n                    b[1] + end_offset_y + offsety,\n                    b[0],\n                    b[1] + offsety\n                );\n            } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) {\n                ctx.moveTo(a[0], a[1] + offsety);\n                var start_offset_x = 0;\n                var start_offset_y = 0;\n                var end_offset_x = 0;\n                var end_offset_y = 0;\n                switch (start_dir) {\n                    case LiteGraph.LEFT:\n                        start_offset_x = -1;\n                        break;\n                    case LiteGraph.RIGHT:\n                        start_offset_x = 1;\n                        break;\n                    case LiteGraph.UP:\n                        start_offset_y = -1;\n                        break;\n                    case LiteGraph.DOWN:\n                        start_offset_y = 1;\n                        break;\n                }\n                switch (end_dir) {\n                    case LiteGraph.LEFT:\n                        end_offset_x = -1;\n                        break;\n                    case LiteGraph.RIGHT:\n                        end_offset_x = 1;\n                        break;\n                    case LiteGraph.UP:\n                        end_offset_y = -1;\n                        break;\n                    case LiteGraph.DOWN:\n                        end_offset_y = 1;\n                        break;\n                }\n                var l = 15;\n                ctx.lineTo(\n                    a[0] + start_offset_x * l,\n                    a[1] + start_offset_y * l + offsety\n                );\n                ctx.lineTo(\n                    b[0] + end_offset_x * l,\n                    b[1] + end_offset_y * l + offsety\n                );\n                ctx.lineTo(b[0], b[1] + offsety);\n            } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) {\n                ctx.moveTo(a[0], a[1]);\n                var start_x = a[0];\n                var start_y = a[1];\n                var end_x = b[0];\n                var end_y = b[1];\n                if (start_dir == LiteGraph.RIGHT) {\n                    start_x += 10;\n                } else {\n                    start_y += 10;\n                }\n                if (end_dir == LiteGraph.LEFT) {\n                    end_x -= 10;\n                } else {\n                    end_y -= 10;\n                }\n                ctx.lineTo(start_x, start_y);\n                ctx.lineTo((start_x + end_x) * 0.5, start_y);\n                ctx.lineTo((start_x + end_x) * 0.5, end_y);\n                ctx.lineTo(end_x, end_y);\n                ctx.lineTo(b[0], b[1]);\n            } else {\n                return;\n            } //unknown\n        }\n\n        //rendering the outline of the connection can be a little bit slow\n        if (\n            this.render_connections_border &&\n            this.ds.scale > 0.6 &&\n            !skip_border\n        ) {\n            ctx.strokeStyle = \"rgba(0,0,0,0.5)\";\n            ctx.stroke();\n        }\n\n        ctx.lineWidth = this.connections_width;\n        ctx.fillStyle = ctx.strokeStyle = color;\n        ctx.stroke();\n        //end line shape\n\n        var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir);\n        if (link && link._pos) {\n            link._pos[0] = pos[0];\n            link._pos[1] = pos[1];\n        }\n\n        //render arrow in the middle\n        if (\n            this.ds.scale >= 0.6 &&\n            this.highquality_render &&\n            end_dir != LiteGraph.CENTER\n        ) {\n            //render arrow\n            if (this.render_connection_arrows) {\n                //compute two points in the connection\n                var posA = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.25,\n                    start_dir,\n                    end_dir\n                );\n                var posB = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.26,\n                    start_dir,\n                    end_dir\n                );\n                var posC = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.75,\n                    start_dir,\n                    end_dir\n                );\n                var posD = this.computeConnectionPoint(\n                    a,\n                    b,\n                    0.76,\n                    start_dir,\n                    end_dir\n                );\n\n                //compute the angle between them so the arrow points in the right direction\n                var angleA = 0;\n                var angleB = 0;\n                if (this.render_curved_connections) {\n                    angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]);\n                    angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]);\n                } else {\n                    angleB = angleA = b[1] > a[1] ? 0 : Math.PI;\n                }\n\n                //render arrow\n                ctx.save();\n                ctx.translate(posA[0], posA[1]);\n                ctx.rotate(angleA);\n                ctx.beginPath();\n                ctx.moveTo(-5, -3);\n                ctx.lineTo(0, +7);\n                ctx.lineTo(+5, -3);\n                ctx.fill();\n                ctx.restore();\n                ctx.save();\n                ctx.translate(posC[0], posC[1]);\n                ctx.rotate(angleB);\n                ctx.beginPath();\n                ctx.moveTo(-5, -3);\n                ctx.lineTo(0, +7);\n                ctx.lineTo(+5, -3);\n                ctx.fill();\n                ctx.restore();\n            }\n\n            //circle\n            ctx.beginPath();\n            ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2);\n            ctx.fill();\n        }\n\n        //render flowing points\n        if (flow) {\n            ctx.fillStyle = color;\n            for (var i = 0; i < 5; ++i) {\n                var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1;\n                var pos = this.computeConnectionPoint(\n                    a,\n                    b,\n                    f,\n                    start_dir,\n                    end_dir\n                );\n                ctx.beginPath();\n                ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI);\n                ctx.fill();\n            }\n        }\n    };\n\n    //returns the link center point based on curvature\n    LGraphCanvas.prototype.computeConnectionPoint = function(\n        a,\n        b,\n        t,\n        start_dir,\n        end_dir\n    ) {\n        start_dir = start_dir || LiteGraph.RIGHT;\n        end_dir = end_dir || LiteGraph.LEFT;\n\n        var dist = distance(a, b);\n        var p0 = a;\n        var p1 = [a[0], a[1]];\n        var p2 = [b[0], b[1]];\n        var p3 = b;\n\n        switch (start_dir) {\n            case LiteGraph.LEFT:\n                p1[0] += dist * -0.25;\n                break;\n            case LiteGraph.RIGHT:\n                p1[0] += dist * 0.25;\n                break;\n            case LiteGraph.UP:\n                p1[1] += dist * -0.25;\n                break;\n            case LiteGraph.DOWN:\n                p1[1] += dist * 0.25;\n                break;\n        }\n        switch (end_dir) {\n            case LiteGraph.LEFT:\n                p2[0] += dist * -0.25;\n                break;\n            case LiteGraph.RIGHT:\n                p2[0] += dist * 0.25;\n                break;\n            case LiteGraph.UP:\n                p2[1] += dist * -0.25;\n                break;\n            case LiteGraph.DOWN:\n                p2[1] += dist * 0.25;\n                break;\n        }\n\n        var c1 = (1 - t) * (1 - t) * (1 - t);\n        var c2 = 3 * ((1 - t) * (1 - t)) * t;\n        var c3 = 3 * (1 - t) * (t * t);\n        var c4 = t * t * t;\n\n        var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0];\n        var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1];\n        return [x, y];\n    };\n\n    LGraphCanvas.prototype.drawExecutionOrder = function(ctx) {\n        ctx.shadowColor = \"transparent\";\n        ctx.globalAlpha = 0.25;\n\n        ctx.textAlign = \"center\";\n        ctx.strokeStyle = \"white\";\n        ctx.globalAlpha = 0.75;\n\n        var visible_nodes = this.visible_nodes;\n        for (var i = 0; i < visible_nodes.length; ++i) {\n            var node = visible_nodes[i];\n            ctx.fillStyle = \"black\";\n            ctx.fillRect(\n                node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT,\n                node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT,\n                LiteGraph.NODE_TITLE_HEIGHT,\n                LiteGraph.NODE_TITLE_HEIGHT\n            );\n            if (node.order == 0) {\n                ctx.strokeRect(\n                    node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,\n                    node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,\n                    LiteGraph.NODE_TITLE_HEIGHT,\n                    LiteGraph.NODE_TITLE_HEIGHT\n                );\n            }\n            ctx.fillStyle = \"#FFF\";\n            ctx.fillText(\n                node.order,\n                node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5,\n                node.pos[1] - 6\n            );\n        }\n        ctx.globalAlpha = 1;\n    };\n\n    /**\n     * draws the widgets stored inside a node\n     * @method drawNodeWidgets\n     **/\n    LGraphCanvas.prototype.drawNodeWidgets = function(\n        node,\n        posY,\n        ctx,\n        active_widget\n    ) {\n        if (!node.widgets || !node.widgets.length) {\n            return 0;\n        }\n        var width = node.size[0];\n        var widgets = node.widgets;\n        posY += 2;\n        var H = LiteGraph.NODE_WIDGET_HEIGHT;\n        var show_text = this.ds.scale > 0.5;\n        ctx.save();\n        ctx.globalAlpha = this.editor_alpha;\n        var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR;\n        var background_color = LiteGraph.WIDGET_BGCOLOR;\n        var text_color = LiteGraph.WIDGET_TEXT_COLOR;\n\t\tvar secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;\n        var margin = 15;\n\n        for (var i = 0; i < widgets.length; ++i) {\n            var w = widgets[i];\n            var y = posY;\n            if (w.y) {\n                y = w.y;\n            }\n            w.last_y = y;\n            ctx.strokeStyle = outline_color;\n            ctx.fillStyle = \"#222\";\n            ctx.textAlign = \"left\";\n\t\t\t//ctx.lineWidth = 2;\n\t\t\tif(w.disabled)\n\t\t\t\tctx.globalAlpha *= 0.5;\n\t\t\tvar widget_width = w.width || width;\n\n            switch (w.type) {\n                case \"button\":\n                    if (w.clicked) {\n                        ctx.fillStyle = \"#AAA\";\n                        w.clicked = false;\n                        this.dirty_canvas = true;\n                    }\n                    ctx.fillRect(margin, y, widget_width - margin * 2, H);\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.strokeRect( margin, y, widget_width - margin * 2, H );\n                    if (show_text) {\n                        ctx.textAlign = \"center\";\n                        ctx.fillStyle = text_color;\n                        ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7);\n                    }\n                    break;\n                case \"toggle\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n                    if (show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]);\n\t\t\t\t\telse\n\t                    ctx.rect(margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.stroke();\n                    ctx.fillStyle = w.value ? \"#89A\" : \"#333\";\n                    ctx.beginPath();\n                    ctx.arc( widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2 );\n                    ctx.fill();\n                    if (show_text) {\n                        ctx.fillStyle = secondary_text_color;\n                        const label = w.label || w.name;    \n                        if (label != null) {\n                            ctx.fillText(label, margin * 2, y + H * 0.7);\n                        }\n                        ctx.fillStyle = w.value ? text_color : secondary_text_color;\n                        ctx.textAlign = \"right\";\n                        ctx.fillText(\n                            w.value\n                                ? w.options.on || \"true\"\n                                : w.options.off || \"false\",\n                            widget_width - 40,\n                            y + H * 0.7\n                        );\n                    }\n                    break;\n                case \"slider\":\n                    ctx.fillStyle = background_color;\n                    ctx.fillRect(margin, y, widget_width - margin * 2, H);\n                    var range = w.options.max - w.options.min;\n                    var nvalue = (w.value - w.options.min) / range;\n\t\t\t\t\tif(nvalue < 0.0) nvalue = 0.0;\n\t\t\t\t\tif(nvalue > 1.0) nvalue = 1.0;\n                    ctx.fillStyle = w.options.hasOwnProperty(\"slider_color\") ? w.options.slider_color : (active_widget == w ? \"#89A\" : \"#678\");\n                    ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H);\n\t\t\t\t\tif(show_text && !w.disabled)\n\t                    ctx.strokeRect(margin, y, widget_width - margin * 2, H);\n                    if (w.marker) {\n                        var marker_nvalue = (w.marker - w.options.min) / range;\n\t\t\t\t\t\tif(marker_nvalue < 0.0) marker_nvalue = 0.0;\n\t\t\t\t\t\tif(marker_nvalue > 1.0) marker_nvalue = 1.0;\n                        ctx.fillStyle = w.options.hasOwnProperty(\"marker_color\") ? w.options.marker_color : \"#AA9\";\n                        ctx.fillRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H );\n                    }\n                    if (show_text) {\n                        ctx.textAlign = \"center\";\n                        ctx.fillStyle = text_color;\n                        ctx.fillText(\n                            w.label || w.name + \"  \" + Number(w.value).toFixed(\n                                                            w.options.precision != null\n                                                                ? w.options.precision\n                                                                : 3\n                                                        ),\n                            widget_width * 0.5,\n                            y + H * 0.7\n                        );\n                    }\n                    break;\n                case \"number\":\n                case \"combo\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n\t\t\t\t\tif(show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5] );\n\t\t\t\t\telse\n\t                    ctx.rect(margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n                    if (show_text) {\n\t\t\t\t\t\tif(!w.disabled)\n\t\t                    ctx.stroke();\n                        ctx.fillStyle = text_color;\n\t\t\t\t\t\tif(!w.disabled)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\t\tctx.moveTo(margin + 16, y + 5);\n\t\t\t\t\t\t\tctx.lineTo(margin + 6, y + H * 0.5);\n\t\t\t\t\t\t\tctx.lineTo(margin + 16, y + H - 5);\n\t\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\t\tctx.moveTo(widget_width - margin - 16, y + 5);\n\t\t\t\t\t\t\tctx.lineTo(widget_width - margin - 6, y + H * 0.5);\n\t\t\t\t\t\t\tctx.lineTo(widget_width - margin - 16, y + H - 5);\n\t\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\t}\n                        ctx.fillStyle = secondary_text_color;\n                        ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7);\n                        ctx.fillStyle = text_color;\n                        ctx.textAlign = \"right\";\n                        if (w.type == \"number\") {\n                            ctx.fillText(\n                                Number(w.value).toFixed(\n                                    w.options.precision !== undefined\n                                        ? w.options.precision\n                                        : 3\n                                ),\n                                widget_width - margin * 2 - 20,\n                                y + H * 0.7\n                            );\n                        } else {\n\t\t\t\t\t\t\tvar v = w.value;\n\t\t\t\t\t\t\tif( w.options.values )\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tvar values = w.options.values;\n\t\t\t\t\t\t\t\tif( values.constructor === Function )\n\t\t\t\t\t\t\t\t\tvalues = values();\n\t\t\t\t\t\t\t\tif(values && values.constructor !== Array)\n\t\t\t\t\t\t\t\t\tv = values[ w.value ];\n\t\t\t\t\t\t\t}\n                            ctx.fillText(\n                                v,\n                                widget_width - margin * 2 - 20,\n                                y + H * 0.7\n                            );\n                        }\n                    }\n                    break;\n                case \"string\":\n                case \"text\":\n                    ctx.textAlign = \"left\";\n                    ctx.strokeStyle = outline_color;\n                    ctx.fillStyle = background_color;\n                    ctx.beginPath();\n                    if (show_text)\n\t                    ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]);\n\t\t\t\t\telse\n\t                    ctx.rect( margin, y, widget_width - margin * 2, H );\n                    ctx.fill();\n\t                if (show_text) {\n\t\t\t\t\t\tif(!w.disabled)\n\t\t\t\t\t\t\tctx.stroke();\n    \t\t\t\t\tctx.save();\n\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\tctx.rect(margin, y, widget_width - margin * 2, H);\n\t\t\t\t\t\tctx.clip();\n\n\t                    //ctx.stroke();\n                        ctx.fillStyle = secondary_text_color;\n                        const label = w.label || w.name;\t\n                        if (label != null) {\n                            ctx.fillText(label, margin * 2, y + H * 0.7);\n                        }\n                        ctx.fillStyle = text_color;\n                        ctx.textAlign = \"right\";\n                        ctx.fillText(String(w.value).substr(0,30), widget_width - margin * 2, y + H * 0.7); //30 chars max\n\t\t\t\t\t\tctx.restore();\n                    }\n                    break;\n                default:\n                    if (w.draw) {\n                        w.draw(ctx, node, widget_width, y, H);\n                    }\n                    break;\n            }\n            posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4;\n\t\t\tctx.globalAlpha = this.editor_alpha;\n\n        }\n        ctx.restore();\n\t\tctx.textAlign = \"left\";\n    };\n\n    /**\n     * process an event on widgets\n     * @method processNodeWidgets\n     **/\n    LGraphCanvas.prototype.processNodeWidgets = function(\n        node,\n        pos,\n        event,\n        active_widget\n    ) {\n        if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) {\n            return null;\n        }\n\n        var x = pos[0] - node.pos[0];\n        var y = pos[1] - node.pos[1];\n        var width = node.size[0];\n        var deltaX = event.deltaX || event.deltax || 0;\n        var that = this;\n        var ref_window = this.getCanvasWindow();\n\n        for (var i = 0; i < node.widgets.length; ++i) {\n            var w = node.widgets[i];\n\t\t\tif(!w || w.disabled)\n\t\t\t\tcontinue;\n\t\t\tvar widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT;\n\t\t\tvar widget_width = w.width || width;\n\t\t\t//outside\n\t\t\tif ( w != active_widget && \n\t\t\t\t(x < 6 || x > widget_width - 12 || y < w.last_y || y > w.last_y + widget_height || w.last_y === undefined) ) \n\t\t\t\tcontinue;\n\n\t\t\tvar old_value = w.value;\n\n            //if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) {\n\t\t\t//inside widget\n\t\t\tswitch (w.type) {\n\t\t\t\tcase \"button\":\n\t\t\t\t\tif (event.type === LiteGraph.pointerevents_method+\"down\") {\n                        if (w.callback) {\n                            setTimeout(function() {\n                                w.callback(w, that, node, pos, event);\n                            }, 20);\n                        }\n                        w.clicked = true;\n                        this.dirty_canvas = true;\n                    }\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"slider\":\n\t\t\t\t\tvar old_value = w.value;\n\t\t\t\t\tvar nvalue = clamp((x - 15) / (widget_width - 30), 0, 1);\n\t\t\t\t\tif(w.options.read_only) break;\n\t\t\t\t\tw.value = w.options.min + (w.options.max - w.options.min) * nvalue;\n\t\t\t\t\tif (old_value != w.value) {\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tinner_value_change(w, w.value);\n\t\t\t\t\t\t}, 20);\n\t\t\t\t\t}\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"number\":\n\t\t\t\tcase \"combo\":\n\t\t\t\t\tvar old_value = w.value;\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"move\" && w.type == \"number\") {\n                        if(deltaX)\n\t\t\t\t\t\t    w.value += deltaX * 0.1 * (w.options.step || 1);\n\t\t\t\t\t\tif ( w.options.min != null && w.value < w.options.min ) {\n\t\t\t\t\t\t\tw.value = w.options.min;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( w.options.max != null && w.value > w.options.max ) {\n\t\t\t\t\t\t\tw.value = w.options.max;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tvar values = w.options.values;\n\t\t\t\t\t\tif (values && values.constructor === Function) {\n\t\t\t\t\t\t\tvalues = w.options.values(w, node);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar values_list = null;\n\t\t\t\t\t\t\n\t\t\t\t\t\tif( w.type != \"number\")\n\t\t\t\t\t\t\tvalues_list = values.constructor === Array ? values : Object.keys(values);\n\n\t\t\t\t\t\tvar delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0;\n\t\t\t\t\t\tif (w.type == \"number\") {\n\t\t\t\t\t\t\tw.value += delta * 0.1 * (w.options.step || 1);\n\t\t\t\t\t\t\tif ( w.options.min != null && w.value < w.options.min ) {\n\t\t\t\t\t\t\t\tw.value = w.options.min;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ( w.options.max != null && w.value > w.options.max ) {\n\t\t\t\t\t\t\t\tw.value = w.options.max;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (delta) { //clicked in arrow, used for combos \n\t\t\t\t\t\t\tvar index = -1;\n\t\t\t\t\t\t\tthis.last_mouseclick = 0; //avoids dobl click event\n\t\t\t\t\t\t\tif(values.constructor === Object)\n\t\t\t\t\t\t\t\tindex = values_list.indexOf( String( w.value ) ) + delta;\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tindex = values_list.indexOf( w.value ) + delta;\n\t\t\t\t\t\t\tif (index >= values_list.length) {\n\t\t\t\t\t\t\t\tindex = values_list.length - 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (index < 0) {\n\t\t\t\t\t\t\t\tindex = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif( values.constructor === Array )\n\t\t\t\t\t\t\t\tw.value = values[index];\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tw.value = index;\n\t\t\t\t\t\t} else { //combo clicked \n\t\t\t\t\t\t\tvar text_values = values != values_list ? Object.values(values) : values;\n\t\t\t\t\t\t\tvar menu = new LiteGraph.ContextMenu(text_values, {\n\t\t\t\t\t\t\t\t\tscale: Math.max(1, this.ds.scale),\n\t\t\t\t\t\t\t\t\tevent: event,\n\t\t\t\t\t\t\t\t\tclassName: \"dark\",\n\t\t\t\t\t\t\t\t\tcallback: inner_clicked.bind(w)\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tref_window);\n\t\t\t\t\t\t\tfunction inner_clicked(v, option, event) {\n\t\t\t\t\t\t\t\tif(values != values_list)\n\t\t\t\t\t\t\t\t\tv = text_values.indexOf(v);\n\t\t\t\t\t\t\t\tthis.value = v;\n\t\t\t\t\t\t\t\tinner_value_change(this, v);\n\t\t\t\t\t\t\t\tthat.dirty_canvas = true;\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} //end mousedown\n\t\t\t\t\telse if(event.type == LiteGraph.pointerevents_method+\"up\" && w.type == \"number\")\n\t\t\t\t\t{\n\t\t\t\t\t\tvar delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0;\n\t\t\t\t\t\tif (event.click_time < 200 && delta == 0) {\n\t\t\t\t\t\t\tthis.prompt(\"Value\",w.value,function(v) {\n\t\t\t\t\t\t\t\t\t// check if v is a valid equation or a number\n\t\t\t\t\t\t\t\t\t  if (/^[0-9+\\-*/()\\s]+|\\d+\\.\\d+$/.test(v)) {\n\t\t\t\t\t\t\t\t\t\ttry {//solve the equation if possible\n\t\t\t\t\t\t\t\t\t    \t\tv = eval(v);\n\t\t\t\t\t\t\t\t\t\t} catch (e) { }\n\t\t\t\t\t\t\t\t\t}\t\n\t\t\t\t\t\t\t\t\tthis.value = Number(v);\n\t\t\t\t\t\t\t\t\tinner_value_change(this, this.value);\n\t\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\t\tevent);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif( old_value != w.value )\n\t\t\t\t\t\tsetTimeout(\n\t\t\t\t\t\t\tfunction() {\n\t\t\t\t\t\t\t\tinner_value_change(this, this.value);\n\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\t20\n\t\t\t\t\t\t);\n\t\t\t\t\tthis.dirty_canvas = true;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"toggle\":\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tw.value = !w.value;\n\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\tinner_value_change(w, w.value);\n\t\t\t\t\t\t}, 20);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"string\":\n\t\t\t\tcase \"text\":\n\t\t\t\t\tif (event.type == LiteGraph.pointerevents_method+\"down\") {\n\t\t\t\t\t\tthis.prompt(\"Value\",w.value,function(v) {\n\t\t\t\t\t\t\t\tinner_value_change(this, v);\n\t\t\t\t\t\t\t}.bind(w),\n\t\t\t\t\t\t\tevent,w.options ? w.options.multiline : false );\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tif (w.mouse) {\n\t\t\t\t\t\tthis.dirty_canvas = w.mouse(event, [x, y], node);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t} //end switch\n\n\t\t\t//value changed\n\t\t\tif( old_value != w.value )\n\t\t\t{\n\t\t\t\tif(node.onWidgetChanged)\n\t\t\t\t\tnode.onWidgetChanged( w.name,w.value,old_value,w );\n                node.graph._version++;\n\t\t\t}\n\n\t\t\treturn w;\n        }//end for\n\n        function inner_value_change(widget, value) {\n            if(widget.type == \"number\"){\n                value = Number(value);\n            }\n            widget.value = value;\n            if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) {\n                node.setProperty( widget.options.property, value );\n            }\n            if (widget.callback) {\n                widget.callback(widget.value, that, node, pos, event);\n            }\n        }\n\n        return null;\n    };\n\n    /**\n     * draws every group area in the background\n     * @method drawGroups\n     **/\n    LGraphCanvas.prototype.drawGroups = function(canvas, ctx) {\n        if (!this.graph) {\n            return;\n        }\n\n        var groups = this.graph._groups;\n\n        ctx.save();\n        ctx.globalAlpha = 0.5 * this.editor_alpha;\n\n        for (var i = 0; i < groups.length; ++i) {\n            var group = groups[i];\n\n            if (!overlapBounding(this.visible_area, group._bounding)) {\n                continue;\n            } //out of the visible area\n\n            ctx.fillStyle = group.color || \"#335\";\n            ctx.strokeStyle = group.color || \"#335\";\n            var pos = group._pos;\n            var size = group._size;\n            ctx.globalAlpha = 0.25 * this.editor_alpha;\n            ctx.beginPath();\n            ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]);\n            ctx.fill();\n            ctx.globalAlpha = this.editor_alpha;\n            ctx.stroke();\n\n            ctx.beginPath();\n            ctx.moveTo(pos[0] + size[0], pos[1] + size[1]);\n            ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]);\n            ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10);\n            ctx.fill();\n\n            var font_size =\n                group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;\n            ctx.font = font_size + \"px Arial\";\n\t\t\tctx.textAlign = \"left\";\n            ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size);\n        }\n\n        ctx.restore();\n    };\n\n    LGraphCanvas.prototype.adjustNodesSize = function() {\n        var nodes = this.graph._nodes;\n        for (var i = 0; i < nodes.length; ++i) {\n            nodes[i].size = nodes[i].computeSize();\n        }\n        this.setDirty(true, true);\n    };\n\n    /**\n     * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode\n     * @method resize\n     **/\n    LGraphCanvas.prototype.resize = function(width, height) {\n        if (!width && !height) {\n            var parent = this.canvas.parentNode;\n            width = parent.offsetWidth;\n            height = parent.offsetHeight;\n        }\n\n        if (this.canvas.width == width && this.canvas.height == height) {\n            return;\n        }\n\n        this.canvas.width = width;\n        this.canvas.height = height;\n        this.bgcanvas.width = this.canvas.width;\n        this.bgcanvas.height = this.canvas.height;\n        this.setDirty(true, true);\n    };\n\n    /**\n     * switches to live mode (node shapes are not rendered, only the content)\n     * this feature was designed when graphs where meant to create user interfaces\n     * @method switchLiveMode\n     **/\n    LGraphCanvas.prototype.switchLiveMode = function(transition) {\n        if (!transition) {\n            this.live_mode = !this.live_mode;\n            this.dirty_canvas = true;\n            this.dirty_bgcanvas = true;\n            return;\n        }\n\n        var self = this;\n        var delta = this.live_mode ? 1.1 : 0.9;\n        if (this.live_mode) {\n            this.live_mode = false;\n            this.editor_alpha = 0.1;\n        }\n\n        var t = setInterval(function() {\n            self.editor_alpha *= delta;\n            self.dirty_canvas = true;\n            self.dirty_bgcanvas = true;\n\n            if (delta < 1 && self.editor_alpha < 0.01) {\n                clearInterval(t);\n                if (delta < 1) {\n                    self.live_mode = true;\n                }\n            }\n            if (delta > 1 && self.editor_alpha > 0.99) {\n                clearInterval(t);\n                self.editor_alpha = 1;\n            }\n        }, 1);\n    };\n\n    LGraphCanvas.prototype.onNodeSelectionChange = function(node) {\n        return; //disabled\n    };\n\n    /* this is an implementation for touch not in production and not ready\n     */\n    /*LGraphCanvas.prototype.touchHandler = function(event) {\n        //alert(\"foo\");\n        var touches = event.changedTouches,\n            first = touches[0],\n            type = \"\";\n\n        switch (event.type) {\n            case \"touchstart\":\n                type = \"mousedown\";\n                break;\n            case \"touchmove\":\n                type = \"mousemove\";\n                break;\n            case \"touchend\":\n                type = \"mouseup\";\n                break;\n            default:\n                return;\n        }\n\n        //initMouseEvent(type, canBubble, cancelable, view, clickCount,\n        //           screenX, screenY, clientX, clientY, ctrlKey,\n        //           altKey, shiftKey, metaKey, button, relatedTarget);\n\n        // this is eventually a Dom object, get the LGraphCanvas back\n        if(typeof this.getCanvasWindow == \"undefined\"){\n            var window = this.lgraphcanvas.getCanvasWindow();\n        }else{\n            var window = this.getCanvasWindow();\n        }\n        \n        var document = window.document;\n\n        var simulatedEvent = document.createEvent(\"MouseEvent\");\n        simulatedEvent.initMouseEvent(\n            type,\n            true,\n            true,\n            window,\n            1,\n            first.screenX,\n            first.screenY,\n            first.clientX,\n            first.clientY,\n            false,\n            false,\n            false,\n            false,\n            0, //left\n            null\n        );\n        first.target.dispatchEvent(simulatedEvent);\n        event.preventDefault();\n    };*/\n\n    /* CONTEXT MENU ********************/\n\n    LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) {\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var group = new LiteGraph.LGraphGroup();\n        group.pos = canvas.convertEventToCanvasOffset(mouse_event);\n        canvas.graph.add(group);\n    };\n\n    /**\n     * Determines the furthest nodes in each direction\n     * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted\n     * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}\n     */\n    LGraphCanvas.getBoundaryNodes = function(nodes) {\n        let top = null;\n        let right = null;\n        let bottom = null;\n        let left = null;\n        for (const nID in nodes) {\n            const node = nodes[nID];\n            const [x, y] = node.pos;\n            const [width, height] = node.size;\n\n            if (top === null || y < top.pos[1]) {\n                top = node;\n            }\n            if (right === null || x + width > right.pos[0] + right.size[0]) {\n                right = node;\n            }\n            if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) {\n                bottom = node;\n            }\n            if (left === null || x < left.pos[0]) {\n                left = node;\n            }\n        }\n\n        return {\n            \"top\": top,\n            \"right\": right,\n            \"bottom\": bottom,\n            \"left\": left\n        };\n    }\n    /**\n     * Determines the furthest nodes in each direction for the currently selected nodes\n     * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}}\n     */\n    LGraphCanvas.prototype.boundaryNodesForSelection = function() {\n        return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes));\n    }\n\n    /**\n     *\n     * @param {LGraphNode[]} nodes a list of nodes\n     * @param {\"top\"|\"bottom\"|\"left\"|\"right\"} direction Direction to align the nodes\n     * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction)\n     */\n    LGraphCanvas.alignNodes = function (nodes, direction, align_to) {\n        if (!nodes) {\n            return;\n        }\n\n        const canvas = LGraphCanvas.active_canvas;\n        let boundaryNodes = []\n        if (align_to === undefined) {\n            boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes)\n        } else {\n            boundaryNodes = {\n                \"top\": align_to,\n                \"right\": align_to,\n                \"bottom\": align_to,\n                \"left\": align_to\n            }\n        }\n\n        for (const [_, node] of Object.entries(canvas.selected_nodes)) {\n            switch (direction) {\n                case \"right\":\n                    node.pos[0] = boundaryNodes[\"right\"].pos[0] + boundaryNodes[\"right\"].size[0] - node.size[0];\n                    break;\n                case \"left\":\n                    node.pos[0] = boundaryNodes[\"left\"].pos[0];\n                    break;\n                case \"top\":\n                    node.pos[1] = boundaryNodes[\"top\"].pos[1];\n                    break;\n                case \"bottom\":\n                    node.pos[1] = boundaryNodes[\"bottom\"].pos[1] + boundaryNodes[\"bottom\"].size[1] - node.size[1];\n                    break;\n            }\n        }\n\n        canvas.dirty_canvas = true;\n        canvas.dirty_bgcanvas = true;\n    };\n\n    LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) {\n        new LiteGraph.ContextMenu([\"Top\", \"Bottom\", \"Left\", \"Right\"], {\n            event: event,\n            callback: inner_clicked,\n            parentMenu: prev_menu,\n        });\n\n        function inner_clicked(value) {\n            LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node);\n        }\n    }\n\n    LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) {\n        new LiteGraph.ContextMenu([\"Top\", \"Bottom\", \"Left\", \"Right\"], {\n            event: event,\n            callback: inner_clicked,\n            parentMenu: prev_menu,\n        });\n\n        function inner_clicked(value) {\n            LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase());\n        }\n    }\n\n    LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) {\n\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n        var graph = canvas.graph;\n        if (!graph)\n            return;\n\n        function inner_onMenuAdded(base_category ,prev_menu){\n    \n            var categories  = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function(category){return category.startsWith(base_category)});\n            var entries = [];\n    \n            categories.map(function(category){\n    \n                if (!category) \n                    return;\n    \n                var base_category_regex = new RegExp('^(' + base_category + ')');\n                var category_name = category.replace(base_category_regex,\"\").split('/')[0];\n                var category_path = base_category  === '' ? category_name + '/' : base_category + category_name + '/';\n    \n                var name = category_name;\n                if(name.indexOf(\"::\") != -1) //in case it has a namespace like \"shader::math/rand\" it hides the namespace\n                    name = name.split(\"::\")[1];\n                        \n                var index = entries.findIndex(function(entry){return entry.value === category_path});\n                if (index === -1) {\n                    entries.push({ value: category_path, content: name, has_submenu: true, callback : function(value, event, mouseEvent, contextMenu){\n                        inner_onMenuAdded(value.value, contextMenu)\n                    }});\n                }\n                \n            });\n    \n            var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter );\n            nodes.map(function(node){\n    \n                if (node.skip_list)\n                    return;\n    \n                var entry = { value: node.type, content: node.title, has_submenu: false , callback : function(value, event, mouseEvent, contextMenu){\n                    \n                        var first_event = contextMenu.getFirstEvent();\n                        canvas.graph.beforeChange();\n                        var node = LiteGraph.createNode(value.value);\n                        if (node) {\n                            node.pos = canvas.convertEventToCanvasOffset(first_event);\n                            canvas.graph.add(node);\n                        }\n                        if(callback)\n                            callback(node);\n                        canvas.graph.afterChange();\n                    \n                    }\n                }\n    \n                entries.push(entry);\n    \n            });\n    \n            new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window );\n    \n        }\n    \n        inner_onMenuAdded('',prev_menu);\n        return false;\n    \n    };\n\n    LGraphCanvas.onMenuCollapseAll = function() {};\n\n    LGraphCanvas.onMenuNodeEdit = function() {};\n\n    LGraphCanvas.showMenuNodeOptionalInputs = function(\n        v,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var options = node.optional_inputs;\n        if (node.onGetInputs) {\n            options = node.onGetInputs();\n        }\n\n        var entries = [];\n        if (options) {\n            for (var i=0; i < options.length; i++) {\n                var entry = options[i];\n                if (!entry) {\n                    entries.push(null);\n                    continue;\n                }\n                var label = entry[0];\n\t\t\t\tif(!entry[2])\n\t\t\t\t\tentry[2] = {};\n\n                if (entry[2].label) {\n                    label = entry[2].label;\n                }\n\n\t\t\t\tentry[2].removable = true;\n                var data = { content: label, value: entry };\n                if (entry[1] == LiteGraph.ACTION) {\n                    data.className = \"event\";\n                }\n                entries.push(data);\n            }\n        }\n\n        if (node.onMenuNodeInputs) {\n            var retEntries = node.onMenuNodeInputs(entries);\n            if(retEntries) entries = retEntries;\n        }\n\n        if (!entries.length) {\n\t\t\tconsole.log(\"no input entries\");\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, e, prev) {\n            if (!node) {\n                return;\n            }\n\n            if (v.callback) {\n                v.callback.call(that, node, v, e, prev);\n            }\n\n            if (v.value) {\n\t\t\t\tnode.graph.beforeChange();\n                node.addInput(v.value[0], v.value[1], v.value[2]);\n\n                if (node.onNodeInputAdd) { // callback to the node when adding a slot\n                    node.onNodeInputAdd(v.value);\n                }\n                node.setDirtyCanvas(true, true);\n\t\t\t\tnode.graph.afterChange();\n            }\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.showMenuNodeOptionalOutputs = function(\n        v,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var options = node.optional_outputs;\n        if (node.onGetOutputs) {\n            options = node.onGetOutputs();\n        }\n\n        var entries = [];\n        if (options) {\n            for (var i=0; i < options.length; i++) {\n                var entry = options[i];\n                if (!entry) {\n                    //separator?\n                    entries.push(null);\n                    continue;\n                }\n\n                if (\n                    node.flags &&\n                    node.flags.skip_repeated_outputs &&\n                    node.findOutputSlot(entry[0]) != -1\n                ) {\n                    continue;\n                } //skip the ones already on\n                var label = entry[0];\n\t\t\t\tif(!entry[2])\n\t\t\t\t\tentry[2] = {};\n                if (entry[2].label) {\n                    label = entry[2].label;\n                }\n\t\t\t\tentry[2].removable = true;\n                var data = { content: label, value: entry };\n                if (entry[1] == LiteGraph.EVENT) {\n                    data.className = \"event\";\n                }\n                entries.push(data);\n            }\n        }\n\n        if (this.onMenuNodeOutputs) {\n            entries = this.onMenuNodeOutputs(entries);\n        }\n        if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted\n            if (node.findOutputSlot(\"onExecuted\") == -1){\n                entries.push({content: \"On Executed\", value: [\"onExecuted\", LiteGraph.EVENT, {nameLocked: true}], className: \"event\"}); //, opts: {}\n            }\n        }\n        // add callback for modifing the menu elements onMenuNodeOutputs\n        if (node.onMenuNodeOutputs) {\n            var retEntries = node.onMenuNodeOutputs(entries);\n            if(retEntries) entries = retEntries;\n        }\n\n        if (!entries.length) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, e, prev) {\n            if (!node) {\n                return;\n            }\n\n            if (v.callback) {\n                v.callback.call(that, node, v, e, prev);\n            }\n\n            if (!v.value) {\n                return;\n            }\n\n            var value = v.value[1];\n\n            if (\n                value &&\n                (value.constructor === Object || value.constructor === Array)\n            ) {\n                //submenu why?\n                var entries = [];\n                for (var i in value) {\n                    entries.push({ content: i, value: value[i] });\n                }\n                new LiteGraph.ContextMenu(entries, {\n                    event: e,\n                    callback: inner_clicked,\n                    parentMenu: prev_menu,\n                    node: node\n                });\n                return false;\n            } else {\n\t\t\t\tnode.graph.beforeChange();\n                node.addOutput(v.value[0], v.value[1], v.value[2]);\n\n                if (node.onNodeOutputAdd) { // a callback to the node when adding a slot\n                    node.onNodeOutputAdd(v.value);\n                }\n                node.setDirtyCanvas(true, true);\n\t\t\t\tnode.graph.afterChange();\n            }\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onShowMenuNodeProperties = function(\n        value,\n        options,\n        e,\n        prev_menu,\n        node\n    ) {\n        if (!node || !node.properties) {\n            return;\n        }\n\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var entries = [];\n        for (var i in node.properties) {\n            var value = node.properties[i] !== undefined ? node.properties[i] : \" \";\n\t\t\tif( typeof value == \"object\" )\n\t\t\t\tvalue = JSON.stringify(value);\n\t\t\tvar info = node.getPropertyInfo(i);\n\t\t\tif(info.type == \"enum\" || info.type == \"combo\")\n\t\t\t\tvalue = LGraphCanvas.getPropertyPrintableValue( value, info.values );\n\n            //value could contain invalid html characters, clean that\n            value = LGraphCanvas.decodeHTML(value);\n            entries.push({\n                content:\n                    \"<span class='property_name'>\" +\n                    (info.label ? info.label : i) +\n                    \"</span>\" +\n                    \"<span class='property_value'>\" +\n                    value +\n                    \"</span>\",\n                value: i\n            });\n        }\n        if (!entries.length) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(\n            entries,\n            {\n                event: e,\n                callback: inner_clicked,\n                parentMenu: prev_menu,\n                allow_html: true,\n                node: node\n            },\n            ref_window\n        );\n\n        function inner_clicked(v, options, e, prev) {\n            if (!node) {\n                return;\n            }\n            var rect = this.getBoundingClientRect();\n            canvas.showEditPropertyValue(node, v.value, {\n                position: [rect.left, rect.top]\n            });\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.decodeHTML = function(str) {\n        var e = document.createElement(\"div\");\n        e.innerText = str;\n        return e.innerHTML;\n    };\n\n    LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) {\n        if (!node) {\n            return;\n        }\n        \n\t\tvar fApplyMultiNode = function(node){\n\t\t\tnode.size = node.computeSize();\n\t\t\tif (node.onResize)\n\t\t\t\tnode.onResize(node.size);\n\t\t}\n\t\t\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.prototype.showLinkMenu = function(link, e) {\n        var that = this;\n\t\t// console.log(link);\n\t\tvar node_left = that.graph.getNodeById( link.origin_id );\n\t\tvar node_right = that.graph.getNodeById( link.target_id );\n\t\tvar fromType = false;\n\t\tif (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type;\n        var destType = false;\n\t\tif (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type;\n\t\t\n\t\tvar options = [\"Add Node\",null,\"Delete\",null];\n\t\t\n\t\t\n        var menu = new LiteGraph.ContextMenu(options, {\n            event: e,\n\t\t\ttitle: link.data != null ? link.data.constructor.name : null,\n            callback: inner_clicked\n        });\n\n        function inner_clicked(v,options,e) {\n            switch (v) {\n                case \"Add Node\":\n\t\t\t\t\tLGraphCanvas.onMenuAdd(null, null, e, menu, function(node){\n\t\t\t\t\t\t// console.debug(\"node autoconnect\");\n\t\t\t\t\t\tif(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length){\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// leave the connection type checking inside connectByType\n\t\t\t\t\t\tif (node_left.connectByType( link.origin_slot, node, fromType )){\n                        \tnode.connectByType( link.target_slot, node_right, destType );\n                            node.pos[0] -= node.size[0] * 0.5;\n                        }\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t\t\n                case \"Delete\":\n                    that.graph.removeLink(link.id);\n                    break;\n                default:\n\t\t\t\t\t/*var nodeCreated = createDefaultNodeForSlot({   nodeFrom: node_left\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotFrom: link.origin_slot\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeTo: node\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,slotTo: link.target_slot\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,e: e\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: \"AUTO\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\tif(nodeCreated) console.log(\"new node in beetween \"+v+\" created\");*/\n            }\n        }\n\n        return false;\n    };\n    \n \tLGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection\n        var optPass = optPass || {};\n        var opts = Object.assign({   nodeFrom: null // input\n                                    ,slotFrom: null // input\n                                    ,nodeTo: null   // output\n                                    ,slotTo: null   // output\n                                    ,position: []\t// pass the event coords\n\t\t\t\t\t\t\t\t  \t,nodeType: null\t// choose a nodetype to add, AUTO to set at first good\n\t\t\t\t\t\t\t\t  \t,posAdd:[0,0]\t// adjust x,y\n\t\t\t\t\t\t\t\t  \t,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h\n                                }\n                                ,optPass\n                            );\n        var that = this;\n        \n        var isFrom = opts.nodeFrom && opts.slotFrom!==null;\n        var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null;\n\t\n        if (!isFrom && !isTo){\n            console.warn(\"No data passed to createDefaultNodeForSlot \"+opts.nodeFrom+\" \"+opts.slotFrom+\" \"+opts.nodeTo+\" \"+opts.slotTo);\n            return false;\n        }\n\t\tif (!opts.nodeType){\n            console.warn(\"No type to createDefaultNodeForSlot\");\n            return false;\n        }\n        \n        var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;\n        var slotX = isFrom ? opts.slotFrom : opts.slotTo;\n        \n        var iSlotConn = false;\n        switch (typeof slotX){\n            case \"string\":\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            case \"object\":\n                // ok slotX\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);\n            break;\n            case \"number\":\n                iSlotConn = slotX;\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n\t\t\tcase \"undefined\":\n            default:\n                // bad ?\n                //iSlotConn = 0;\n                console.warn(\"Cant get slot information \"+slotX);\n                return false;\n        }\n\t\n\t\tif (slotX===false || iSlotConn===false){\n\t\t\tconsole.warn(\"createDefaultNodeForSlot bad slotX \"+slotX+\" \"+iSlotConn);\n\t\t}\n\t\t\n\t\t// check for defaults nodes for this slottype\n\t\tvar fromSlotType = slotX.type==LiteGraph.EVENT?\"_event_\":slotX.type;\n\t\tvar slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;\n\t\tif(slotTypesDefault && slotTypesDefault[fromSlotType]){\n\t\t\tif (slotX.link !== null) {\n\t\t\t\t// is connected\n\t\t\t}else{\n\t\t\t\t// is not not connected\n\t\t\t}\n\t\t\tnodeNewType = false;\n\t\t\tif(typeof slotTypesDefault[fromSlotType] == \"object\" || typeof slotTypesDefault[fromSlotType] == \"array\"){\n\t\t\t\tfor(var typeX in slotTypesDefault[fromSlotType]){\n\t\t\t\t\tif (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == \"AUTO\"){\n\t\t\t\t\t\tnodeNewType = slotTypesDefault[fromSlotType][typeX];\n\t\t\t\t\t\t// console.log(\"opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: \"+opts.nodeType);\n\t\t\t\t\t\tbreak; // --------\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\tif (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == \"AUTO\") nodeNewType = slotTypesDefault[fromSlotType];\n\t\t\t}\n\t\t\tif (nodeNewType) {\n\t\t\t\tvar nodeNewOpts = false;\n\t\t\t\tif (typeof nodeNewType == \"object\" && nodeNewType.node){\n\t\t\t\t\tnodeNewOpts = nodeNewType;\n\t\t\t\t\tnodeNewType = nodeNewType.node;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t//that.graph.beforeChange();\n\t\t\t\t\n\t\t\t\tvar newNode = LiteGraph.createNode(nodeNewType);\n\t\t\t\tif(newNode){\n\t\t\t\t\t// if is object pass options\n\t\t\t\t\tif (nodeNewOpts){\n\t\t\t\t\t\tif (nodeNewOpts.properties) {\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.properties) {\n\t\t\t\t\t\t\t\tnewNode.addProperty( i, nodeNewOpts.properties[i] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.inputs) {\n\t\t\t\t\t\t\tnewNode.inputs = [];\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.inputs) {\n\t\t\t\t\t\t\t\tnewNode.addOutput(\n\t\t\t\t\t\t\t\t\tnodeNewOpts.inputs[i][0],\n\t\t\t\t\t\t\t\t\tnodeNewOpts.inputs[i][1]\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.outputs) {\n\t\t\t\t\t\t\tnewNode.outputs = [];\n\t\t\t\t\t\t\tfor (var i in nodeNewOpts.outputs) {\n\t\t\t\t\t\t\t\tnewNode.addOutput(\n\t\t\t\t\t\t\t\t\tnodeNewOpts.outputs[i][0],\n\t\t\t\t\t\t\t\t\tnodeNewOpts.outputs[i][1]\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.title) {\n\t\t\t\t\t\t\tnewNode.title = nodeNewOpts.title;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (nodeNewOpts.json) {\n\t\t\t\t\t\t\tnewNode.configure(nodeNewOpts.json);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// add the node\n\t\t\t\t\tthat.graph.add(newNode);\n\t\t\t\t\tnewNode.pos = [\topts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0)\n\t\t\t\t\t\t\t\t   \t,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/\n\t\t\t\t\t\n\t\t\t\t\t//that.graph.afterChange();\n\t\t\t\t\t\n\t\t\t\t\t// connect the two!\n\t\t\t\t\tif (isFrom){\n\t\t\t\t\t\topts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType );\n\t\t\t\t\t}else{\n\t\t\t\t\t\topts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType );\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// if connecting in between\n\t\t\t\t\tif (isFrom && isTo){\n\t\t\t\t\t\t// TODO\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treturn true;\n\t\t\t\t\t\n\t\t\t\t}else{\n\t\t\t\t\tconsole.log(\"failed creating \"+nodeNewType);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n \n    LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection\n        var optPass = optPass || {};\n        var opts = Object.assign({   nodeFrom: null  // input\n                                    ,slotFrom: null // input\n                                    ,nodeTo: null   // output\n                                    ,slotTo: null   // output\n                                    ,e: null\n                                }\n                                ,optPass\n                            );\n        var that = this;\n        \n        var isFrom = opts.nodeFrom && opts.slotFrom;\n        var isTo = !isFrom && opts.nodeTo && opts.slotTo;\n        \n        if (!isFrom && !isTo){\n            console.warn(\"No data passed to showConnectionMenu\");\n            return false;\n        }\n        \n        var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;\n        var slotX = isFrom ? opts.slotFrom : opts.slotTo;\n        \n        var iSlotConn = false;\n        switch (typeof slotX){\n            case \"string\":\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            case \"object\":\n                // ok slotX\n                iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);\n            break;\n            case \"number\":\n                iSlotConn = slotX;\n                slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];\n            break;\n            default:\n                // bad ?\n                //iSlotConn = 0;\n                console.warn(\"Cant get slot information \"+slotX);\n                return false;\n        }\n            \n\t\tvar options = [\"Add Node\",null];\n\t\t\n\t\tif (that.allow_searchbox){\n\t\t\toptions.push(\"Search\");\n\t\t\toptions.push(null);\n\t\t}\n\t\t\n\t\t// get defaults nodes for this slottype\n\t\tvar fromSlotType = slotX.type==LiteGraph.EVENT?\"_event_\":slotX.type;\n\t\tvar slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;\n\t\tif(slotTypesDefault && slotTypesDefault[fromSlotType]){\n\t\t\tif(typeof slotTypesDefault[fromSlotType] == \"object\" || typeof slotTypesDefault[fromSlotType] == \"array\"){\n\t\t\t\tfor(var typeX in slotTypesDefault[fromSlotType]){\n\t\t\t\t\toptions.push(slotTypesDefault[fromSlotType][typeX]);\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\toptions.push(slotTypesDefault[fromSlotType]);\n\t\t\t}\n\t\t}\n\t\t\n\t\t// build menu\n        var menu = new LiteGraph.ContextMenu(options, {\n            event: opts.e,\n\t\t\ttitle: (slotX && slotX.name!=\"\" ? (slotX.name + (fromSlotType?\" | \":\"\")) : \"\")+(slotX && fromSlotType ? fromSlotType : \"\"),\n            callback: inner_clicked\n        });\n        \n\t\t// callback\n        function inner_clicked(v,options,e) {\n            //console.log(\"Process showConnectionMenu selection\");\n            switch (v) {\n                case \"Add Node\":\n                    LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){\n                        if (isFrom){\n                            opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType );\n                        }else{\n                            opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType );\n                        }\n                    });\n                    break;\n\t\t\t\tcase \"Search\":\n\t\t\t\t\tif(isFrom){\n\t\t\t\t\t\tthat.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType});\n\t\t\t\t\t}else{\n\t\t\t\t\t\tthat.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType});\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n                default:\n\t\t\t\t\t// check for defaults nodes for this slottype\n\t\t\t\t\tvar nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t,nodeType: v\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\tif (nodeCreated){\n\t\t\t\t\t\t// new node created\n\t\t\t\t\t\t//console.log(\"node \"+v+\" created\")\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// failed or v is not in defaults\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n            }\n        }   \n        \n        return false;\n    };\n\n    // TODO refactor :: this is used fot title but not for properties!\n    LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) {\n        var input_html = \"\";\n        var property = item.property || \"title\";\n        var value = node[property];\n\n        // TODO refactor :: use createDialog ?\n        \n        var dialog = document.createElement(\"div\");\n        dialog.is_modified = false;\n        dialog.className = \"graphdialog\";\n        dialog.innerHTML =\n            \"<span class='name'></span><input autofocus type='text' class='value'/><button>OK</button>\";\n        dialog.close = function() {\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n        var title = dialog.querySelector(\".name\");\n        title.innerText = property;\n        var input = dialog.querySelector(\".value\");\n        if (input) {\n            input.value = value;\n            input.addEventListener(\"blur\", function(e) {\n                this.focus();\n            });\n            input.addEventListener(\"keydown\", function(e) {\n                dialog.is_modified = true;\n                if (e.keyCode == 27) {\n                    //ESC\n                    dialog.close();\n                } else if (e.keyCode == 13) {\n                    inner(); // save\n                } else if (e.keyCode != 13 && e.target.localName != \"textarea\") {\n                    return;\n                }\n                e.preventDefault();\n                e.stopPropagation();\n            });\n        }\n\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n\n        var rect = canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", inner);\n        canvas.parentNode.appendChild(dialog);\n\n        if(input) input.focus();\n        \n        var dialogCloseTimer = null;\n        dialog.addEventListener(\"mouseleave\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        dialog.addEventListener(\"mouseenter\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        \n        function inner() {\n            if(input) setValue(input.value);\n        }\n\n        function setValue(value) {\n            if (item.type == \"Number\") {\n                value = Number(value);\n            } else if (item.type == \"Boolean\") {\n                value = Boolean(value);\n            }\n            node[property] = value;\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n            node.setDirtyCanvas(true, true);\n        }\n    };\n\n    // refactor: there are different dialogs, some uses createDialog some dont\n    LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) {\n        var that = this;\n        var input_html = \"\";\n        title = title || \"\";\n\n        var dialog = document.createElement(\"div\");\n        dialog.is_modified = false;\n        dialog.className = \"graphdialog rounded\";\n        if(multiline)\n\t        dialog.innerHTML = \"<span class='name'></span> <textarea autofocus class='value'></textarea><button class='rounded'>OK</button>\";\n\t\telse\n        \tdialog.innerHTML = \"<span class='name'></span> <input autofocus type='text' class='value'/><button class='rounded'>OK</button>\";\n        dialog.close = function() {\n            that.prompt_box = null;\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n        canvas.parentNode.appendChild(dialog);\n        \n        if (this.ds.scale > 1) {\n            dialog.style.transform = \"scale(\" + this.ds.scale + \")\";\n        }\n\n        var dialogCloseTimer = null;\n        var prevent_timeout = false;\n        LiteGraph.pointerListenerAdd(dialog,\"leave\", function(e) {\n            if (prevent_timeout)\n                return;\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        LiteGraph.pointerListenerAdd(dialog,\"enter\", function(e) {\n            if(LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        var selInDia = dialog.querySelectorAll(\"select\");\n        if (selInDia){\n            // if filtering, check focus changed to comboboxes and prevent closing\n            selInDia.forEach(function(selIn) {\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            });\n        }\n\n        if (that.prompt_box) {\n            that.prompt_box.close();\n        }\n        that.prompt_box = dialog;\n\n        var first = null;\n        var timeout = null;\n        var selected = null;\n\n        var name_element = dialog.querySelector(\".name\");\n        name_element.innerText = title;\n        var value_element = dialog.querySelector(\".value\");\n        value_element.value = value;\n\n        var input = value_element;\n        input.addEventListener(\"keydown\", function(e) {\n            dialog.is_modified = true;\n            if (e.keyCode == 27) {\n                //ESC\n                dialog.close();\n            } else if (e.keyCode == 13 && e.target.localName != \"textarea\") {\n                if (callback) {\n                    callback(this.value);\n                }\n                dialog.close();\n            } else {\n                return;\n            }\n            e.preventDefault();\n            e.stopPropagation();\n        });\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", function(e) {\n            if (callback) {\n                callback(input.value);\n            }\n            that.setDirty(true);\n            dialog.close();\n        });\n\n        var rect = canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n\n        setTimeout(function() {\n            input.focus();\n        }, 10);\n\n        return dialog;\n    };\n\n    LGraphCanvas.search_limit = -1;\n    LGraphCanvas.prototype.showSearchBox = function(event, options) {\n        // proposed defaults\n        var def_options = { slot_from: null\n                        ,node_from: null\n                        ,node_to: null\n                        ,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out\n                        ,type_filter_in: false                          // these are default: pass to set initially set values\n                        ,type_filter_out: false\n                        ,show_general_if_none_on_typefilter: true\n                        ,show_general_after_typefiltered: true\n                        ,hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave\n                        ,show_all_if_empty: true\n                        ,show_all_on_open: LiteGraph.search_show_all_on_open\n                    };\n        options = Object.assign(def_options, options || {});\n        \n\t\t//console.log(options);\n\t\t\n        var that = this;\n        var input_html = \"\";\n        var graphcanvas = LGraphCanvas.active_canvas;\n        var canvas = graphcanvas.canvas;\n        var root_document = canvas.ownerDocument || document;\n\n        var dialog = document.createElement(\"div\");\n        dialog.className = \"litegraph litesearchbox graphdialog rounded\";\n        dialog.innerHTML = \"<span class='name'>Search</span> <input autofocus type='text' class='value rounded'/>\";\n        if (options.do_type_filter){\n            dialog.innerHTML += \"<select class='slot_in_type_filter'><option value=''></option></select>\";\n            dialog.innerHTML += \"<select class='slot_out_type_filter'><option value=''></option></select>\";\n        }\n        dialog.innerHTML += \"<div class='helper'></div>\";\n        \n        if( root_document.fullscreenElement )\n\t        root_document.fullscreenElement.appendChild(dialog);\n\t\telse\n\t\t{\n\t\t    root_document.body.appendChild(dialog);\n\t\t\troot_document.body.style.overflow = \"hidden\";\n\t\t}\n        // dialog element has been appended\n        \n        if (options.do_type_filter){\n            var selIn = dialog.querySelector(\".slot_in_type_filter\");\n            var selOut = dialog.querySelector(\".slot_out_type_filter\");\n        }\n        \n        dialog.close = function() {\n            that.search_box = null;\n\t\t\tthis.blur();\n            canvas.focus();\n\t\t\troot_document.body.style.overflow = \"\";\n\n            setTimeout(function() {\n                that.canvas.focus();\n            }, 20); //important, if canvas loses focus keys wont be captured\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n\n        if (this.ds.scale > 1) {\n            dialog.style.transform = \"scale(\" + this.ds.scale + \")\";\n        }\n\n        // hide on mouse leave\n        if(options.hide_on_mouse_leave){\n            var prevent_timeout = false;\n            var timeout_close = null;\n            LiteGraph.pointerListenerAdd(dialog,\"enter\", function(e) {\n                if (timeout_close) {\n                    clearTimeout(timeout_close);\n                    timeout_close = null;\n                }\n            });\n            LiteGraph.pointerListenerAdd(dialog,\"leave\", function(e) {\n                if (prevent_timeout){\n                    return;\n                }\n                timeout_close = setTimeout(function() {\n                    dialog.close();\n                }, 500);\n            });\n            // if filtering, check focus changed to comboboxes and prevent closing\n            if (options.do_type_filter){\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n                selOut.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selOut.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selOut.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            }\n        }\n\n        if (that.search_box) {\n            that.search_box.close();\n        }\n        that.search_box = dialog;\n\n        var helper = dialog.querySelector(\".helper\");\n\n        var first = null;\n        var timeout = null;\n        var selected = null;\n\n        var input = dialog.querySelector(\"input\");\n        if (input) {\n            input.addEventListener(\"blur\", function(e) {\n                if(that.search_box)\n                    this.focus();\n            });\n            input.addEventListener(\"keydown\", function(e) {\n                if (e.keyCode == 38) {\n                    //UP\n                    changeSelection(false);\n                } else if (e.keyCode == 40) {\n                    //DOWN\n                    changeSelection(true);\n                } else if (e.keyCode == 27) {\n                    //ESC\n                    dialog.close();\n                } else if (e.keyCode == 13) {\n                    refreshHelper();\n                    if (selected) {\n                        select(selected.innerHTML);\n                    } else if (first) {\n                        select(first);\n                    } else {\n                        dialog.close();\n                    }\n                } else {\n                    if (timeout) {\n                        clearInterval(timeout);\n                    }\n                    timeout = setTimeout(refreshHelper, 250);\n                    return;\n                }\n                e.preventDefault();\n                e.stopPropagation();\n\t\t\t\te.stopImmediatePropagation();\n\t\t\t\treturn true;\n            });\n        }\n        \n        // if should filter on type, load and fill selected and choose elements if passed\n        if (options.do_type_filter){\n            if (selIn){\n                var aSlots = LiteGraph.slot_types_in;\n                var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length;\n                \n                if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION)\n                    options.type_filter_in = \"_event_\";\n                /* this will filter on * .. but better do it manually in case\n                else if(options.type_filter_in === \"\" || options.type_filter_in === 0)\n                    options.type_filter_in = \"*\";*/\n                \n                for (var iK=0; iK<nSlots; iK++){\n                    var opt = document.createElement('option');\n                    opt.value = aSlots[iK];\n                    opt.innerHTML = aSlots[iK];\n                    selIn.appendChild(opt);\n                    if(options.type_filter_in !==false && (options.type_filter_in+\"\").toLowerCase() == (aSlots[iK]+\"\").toLowerCase()){\n                        //selIn.selectedIndex ..\n                        opt.selected = true;\n\t\t\t\t\t\t//console.log(\"comparing IN \"+options.type_filter_in+\" :: \"+aSlots[iK]);\n\t                }else{\n\t\t\t\t\t\t//console.log(\"comparing OUT \"+options.type_filter_in+\" :: \"+aSlots[iK]);\n\t\t\t\t\t}\n\t\t\t\t}\n                selIn.addEventListener(\"change\",function(){\n                    refreshHelper();\n                });\n            }\n            if (selOut){\n                var aSlots = LiteGraph.slot_types_out;\n                var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; \n                \n                if (options.type_filter_out == LiteGraph.EVENT || options.type_filter_out == LiteGraph.ACTION)\n                    options.type_filter_out = \"_event_\";\n                /* this will filter on * .. but better do it manually in case\n                else if(options.type_filter_out === \"\" || options.type_filter_out === 0)\n                    options.type_filter_out = \"*\";*/\n                \n                for (var iK=0; iK<nSlots; iK++){\n                    var opt = document.createElement('option');\n                    opt.value = aSlots[iK];\n                    opt.innerHTML = aSlots[iK];\n                    selOut.appendChild(opt);\n                    if(options.type_filter_out !==false && (options.type_filter_out+\"\").toLowerCase() == (aSlots[iK]+\"\").toLowerCase()){\n                        //selOut.selectedIndex ..\n                        opt.selected = true;\n                    }\n                }\n                selOut.addEventListener(\"change\",function(){\n                    refreshHelper();\n                });\n            }\n        }\n        \n        //compute best position\n        var rect = canvas.getBoundingClientRect();\n\n        var left = ( event ? event.clientX : (rect.left + rect.width * 0.5) ) - 80;\n        var top = ( event ? event.clientY : (rect.top + rect.height * 0.5) ) - 20;\n        dialog.style.left = left + \"px\";\n        dialog.style.top = top + \"px\";\n\n\t\t//To avoid out of screen problems\n\t\tif(event.layerY > (rect.height - 200)) \n            helper.style.maxHeight = (rect.height - event.layerY - 20) + \"px\";\n\n\t\t/*\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (event) {\n            dialog.style.left = event.clientX + offsetx + \"px\";\n            dialog.style.top = event.clientY + offsety + \"px\";\n        } else {\n            dialog.style.left = canvas.width * 0.5 + offsetx + \"px\";\n            dialog.style.top = canvas.height * 0.5 + offsety + \"px\";\n        }\n        canvas.parentNode.appendChild(dialog);\n\t\t*/\n\n        input.focus();\n        if (options.show_all_on_open) refreshHelper();\n\n        function select(name) {\n            if (name) {\n                if (that.onSearchBoxSelection) {\n                    that.onSearchBoxSelection(name, event, graphcanvas);\n                } else {\n                    var extra = LiteGraph.searchbox_extras[name.toLowerCase()];\n                    if (extra) {\n                        name = extra.type;\n                    }\n\n\t\t\t\t\tgraphcanvas.graph.beforeChange();\n                    var node = LiteGraph.createNode(name);\n                    if (node) {\n                        node.pos = graphcanvas.convertEventToCanvasOffset(\n                            event\n                        );\n                        graphcanvas.graph.add(node, false);\n                    }\n\n                    if (extra && extra.data) {\n                        if (extra.data.properties) {\n                            for (var i in extra.data.properties) {\n                                node.addProperty( i, extra.data.properties[i] );\n                            }\n                        }\n                        if (extra.data.inputs) {\n                            node.inputs = [];\n                            for (var i in extra.data.inputs) {\n                                node.addOutput(\n                                    extra.data.inputs[i][0],\n                                    extra.data.inputs[i][1]\n                                );\n                            }\n                        }\n                        if (extra.data.outputs) {\n                            node.outputs = [];\n                            for (var i in extra.data.outputs) {\n                                node.addOutput(\n                                    extra.data.outputs[i][0],\n                                    extra.data.outputs[i][1]\n                                );\n                            }\n                        }\n                        if (extra.data.title) {\n                            node.title = extra.data.title;\n                        }\n                        if (extra.data.json) {\n                            node.configure(extra.data.json);\n                        }\n\n                    }\n\n                    // join node after inserting\n                    if (options.node_from){\n                        var iS = false;\n                        switch (typeof options.slot_from){\n                            case \"string\":\n                                iS = options.node_from.findOutputSlot(options.slot_from);    \n                            break;\n                            case \"object\":\n                                if (options.slot_from.name){\n                                    iS = options.node_from.findOutputSlot(options.slot_from.name);\n                                }else{\n                                    iS = -1;\n                                }\n                                if (iS==-1 && typeof options.slot_from.slot_index !== \"undefined\") iS = options.slot_from.slot_index;\n                            break;\n                            case \"number\":\n                                iS = options.slot_from;\n                            break;\n                            default:\n                                iS = 0; // try with first if no name set\n                        }\n                        if (typeof options.node_from.outputs[iS] !== \"undefined\"){\n                            if (iS!==false && iS>-1){\n                                options.node_from.connectByType( iS, node, options.node_from.outputs[iS].type );\n                            }\n                        }else{\n                            // console.warn(\"cant find slot \" + options.slot_from);\n                        }\n                    }\n                    if (options.node_to){\n                        var iS = false;\n                        switch (typeof options.slot_from){\n                            case \"string\":\n                                iS = options.node_to.findInputSlot(options.slot_from);    \n                            break;\n                            case \"object\":\n                                if (options.slot_from.name){\n                                    iS = options.node_to.findInputSlot(options.slot_from.name);\n                                }else{\n                                    iS = -1;\n                                }\n                                if (iS==-1 && typeof options.slot_from.slot_index !== \"undefined\") iS = options.slot_from.slot_index;\n                            break;\n                            case \"number\":\n                                iS = options.slot_from;\n                            break;\n                            default:\n                                iS = 0; // try with first if no name set\n                        }\n                        if (typeof options.node_to.inputs[iS] !== \"undefined\"){\n                            if (iS!==false && iS>-1){\n                                // try connection\n                                options.node_to.connectByTypeOutput(iS,node,options.node_to.inputs[iS].type);\n                            }\n                        }else{\n                            // console.warn(\"cant find slot_nodeTO \" + options.slot_from);\n                        }\n                    }\n                    \n                    graphcanvas.graph.afterChange();\n                }\n            }\n\n            dialog.close();\n        }\n\n        function changeSelection(forward) {\n            var prev = selected;\n            if (selected) {\n                selected.classList.remove(\"selected\");\n            }\n            if (!selected) {\n                selected = forward\n                    ? helper.childNodes[0]\n                    : helper.childNodes[helper.childNodes.length];\n            } else {\n                selected = forward\n                    ? selected.nextSibling\n                    : selected.previousSibling;\n                if (!selected) {\n                    selected = prev;\n                }\n            }\n            if (!selected) {\n                return;\n            }\n            selected.classList.add(\"selected\");\n            selected.scrollIntoView({block: \"end\", behavior: \"smooth\"});\n        }\n\n        function refreshHelper() {\n            timeout = null;\n            var str = input.value;\n            first = null;\n            helper.innerHTML = \"\";\n            if (!str && !options.show_all_if_empty) {\n                return;\n            }\n\n            if (that.onSearchBox) {\n                var list = that.onSearchBox(helper, str, graphcanvas);\n                if (list) {\n                    for (var i = 0; i < list.length; ++i) {\n                        addResult(list[i]);\n                    }\n                }\n            } else {\n                var c = 0;\n                str = str.toLowerCase();\n\t\t\t\tvar filter = graphcanvas.filter || graphcanvas.graph.filter;\n\n                // filter by type preprocess\n                if(options.do_type_filter && that.search_box){\n                    var sIn = that.search_box.querySelector(\".slot_in_type_filter\");\n                    var sOut = that.search_box.querySelector(\".slot_out_type_filter\");\n                }else{\n                    var sIn = false;\n                    var sOut = false;\n                }\n                \n                //extras\n                for (var i in LiteGraph.searchbox_extras) {\n                    var extra = LiteGraph.searchbox_extras[i];\n                    if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) {\n                        continue;\n                    }\n\t\t\t\t\tvar ctor = LiteGraph.registered_node_types[ extra.type ];\n\t\t\t\t\tif( ctor && ctor.filter != filter )\n\t\t\t\t\t\tcontinue;\n                    if( ! inner_test_filter(extra.type) )\n                        continue;\n                    addResult( extra.desc, \"searchbox_extra\" );\n                    if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                        break;\n                    }\n                }\n\n\t\t\t\tvar filtered = null;\n                if (Array.prototype.filter) { //filter supported\n                    var keys = Object.keys( LiteGraph.registered_node_types ); //types\n                    var filtered = keys.filter( inner_test_filter );\n                } else {\n\t\t\t\t\tfiltered = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i) )\n\t\t\t\t\t\t\tfiltered.push(i);\n                    }\n                }\n\n\t\t\t\tfor (var i = 0; i < filtered.length; i++) {\n\t\t\t\t\taddResult(filtered[i]);\n\t\t\t\t\tif ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n                \n                // add general type if filtering\n                if (options.show_general_after_typefiltered\n                    && (sIn.value || sOut.value) \n                ){\n                    filtered_extra = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?\"*\":false, outTypeOverride: sOut&&sOut.value?\"*\":false}) )\n\t\t\t\t\t\t\tfiltered_extra.push(i);\n                    }\n                    for (var i = 0; i < filtered_extra.length; i++) {\n                        addResult(filtered_extra[i], \"generic_type\");\n                        if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                            break;\n                        }\n                    }\n                }\n                \n                // check il filtering gave no results\n                if ((sIn.value || sOut.value) && \n                    ( (helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) )\n                ){\n                    filtered_extra = [];\n                    for (var i in LiteGraph.registered_node_types) {\n\t\t\t\t\t\tif( inner_test_filter(i, {skipFilter: true}) )\n\t\t\t\t\t\t\tfiltered_extra.push(i);\n                    }\n                    for (var i = 0; i < filtered_extra.length; i++) {\n                        addResult(filtered_extra[i], \"not_in_filter\");\n                        if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) {\n                            break;\n                        }\n                    }\n                }\n                \n\t\t\t\tfunction inner_test_filter( type, optsIn )\n\t\t\t\t{\n                    var optsIn = optsIn || {};\n                    var optsDef = { skipFilter: false\n                                    ,inTypeOverride: false\n                                    ,outTypeOverride: false\n                                  };\n                    var opts = Object.assign(optsDef,optsIn);\n\t\t\t\t\tvar ctor = LiteGraph.registered_node_types[ type ];\n\t\t\t\t\tif(filter && ctor.filter != filter )\n\t\t\t\t\t\treturn false;\n                    if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1)\n                        return false;\n                    \n                    // filter by slot IN, OUT types\n                    if(options.do_type_filter && !opts.skipFilter){\n                        var sType = type;\n                        \n                        var sV = sIn.value;\n                        if (opts.inTypeOverride!==false) sV = opts.inTypeOverride;\n\t\t\t\t\t\t//if (sV.toLowerCase() == \"_event_\") sV = LiteGraph.EVENT; // -1\n                        \n                        if(sIn && sV){\n                            //console.log(\"will check filter against \"+sV);\n                            if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored\n                                //console.debug(\"check \"+sType+\" in \"+LiteGraph.registered_slot_in_types[sV].nodes);\n                                var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType);\n                                if (doesInc!==false){\n                                    //console.log(sType+\" HAS \"+sV);\n                                }else{\n                                    /*console.debug(LiteGraph.registered_slot_in_types[sV]);\n                                    console.log(+\" DONT includes \"+type);*/\n                                    return false;\n                                }\n                            }\n                        }\n                        \n                        var sV = sOut.value;\n                        if (opts.outTypeOverride!==false) sV = opts.outTypeOverride;\n                        //if (sV.toLowerCase() == \"_event_\") sV = LiteGraph.EVENT; // -1\n                        \n                        if(sOut && sV){\n                            //console.log(\"search will check filter against \"+sV);\n                            if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored\n                                //console.debug(\"check \"+sType+\" in \"+LiteGraph.registered_slot_out_types[sV].nodes);\n                                var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType);\n                                if (doesInc!==false){\n                                    //console.log(sType+\" HAS \"+sV);\n                                }else{\n                                    /*console.debug(LiteGraph.registered_slot_out_types[sV]);\n                                    console.log(+\" DONT includes \"+type);*/\n                                    return false;\n                                }\n                            }\n                        }\n                    }\n                    return true;\n\t\t\t\t}\n            }\n\n            function addResult(type, className) {\n                var help = document.createElement(\"div\");\n                if (!first) {\n                    first = type;\n                }\n                help.innerText = type;\n                help.dataset[\"type\"] = escape(type);\n                help.className = \"litegraph lite-search-item\";\n                if (className) {\n                    help.className += \" \" + className;\n                }\n                help.addEventListener(\"click\", function(e) {\n                    select(unescape(this.dataset[\"type\"]));\n                });\n                helper.appendChild(help);\n            }\n        }\n\n        return dialog;\n    };\n\n    LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) {\n        if (!node || node.properties[property] === undefined) {\n            return;\n        }\n\n        options = options || {};\n        var that = this;\n\n        var info = node.getPropertyInfo(property);\n\t\tvar type = info.type;\n\n        var input_html = \"\";\n\n        if (type == \"string\" || type == \"number\" || type == \"array\" || type == \"object\") {\n            input_html = \"<input autofocus type='text' class='value'/>\";\n        } else if ( (type == \"enum\" || type == \"combo\") && info.values) {\n            input_html = \"<select autofocus type='text' class='value'>\";\n            for (var i in info.values) {\n                var v = i;\n\t\t\t\tif( info.values.constructor === Array )\n\t\t\t\t\tv = info.values[i];\n\n                input_html +=\n                    \"<option value='\" +\n                    v +\n                    \"' \" +\n                    (v == node.properties[property] ? \"selected\" : \"\") +\n                    \">\" +\n                    info.values[i] +\n                    \"</option>\";\n            }\n            input_html += \"</select>\";\n        } else if (type == \"boolean\" || type == \"toggle\") {\n            input_html =\n                \"<input autofocus type='checkbox' class='value' \" +\n                (node.properties[property] ? \"checked\" : \"\") +\n                \"/>\";\n        } else {\n            console.warn(\"unknown type: \" + type);\n            return;\n        }\n\n        var dialog = this.createDialog(\n            \"<span class='name'>\" +\n                (info.label ? info.label : property) +\n                \"</span>\" +\n                input_html +\n                \"<button>OK</button>\",\n            options\n        );\n\n        var input = false;\n        if ((type == \"enum\" || type == \"combo\") && info.values) {\n            input = dialog.querySelector(\"select\");\n            input.addEventListener(\"change\", function(e) {\n                dialog.modified();\n                setValue(e.target.value);\n                //var index = e.target.value;\n                //setValue( e.options[e.selectedIndex].value );\n            });\n        } else if (type == \"boolean\" || type == \"toggle\") {\n            input = dialog.querySelector(\"input\");\n            if (input) {\n                input.addEventListener(\"click\", function(e) {\n                    dialog.modified();\n                    setValue(!!input.checked);\n                });\n            }\n        } else {\n            input = dialog.querySelector(\"input\");\n            if (input) {\n                input.addEventListener(\"blur\", function(e) {\n                    this.focus();\n                });\n\n\t\t\t\tvar v = node.properties[property] !== undefined ? node.properties[property] : \"\";\n\t\t\t\tif (type !== 'string') {\n                    v = JSON.stringify(v);\n                }\n\n                input.value = v;\n                input.addEventListener(\"keydown\", function(e) {\n                    if (e.keyCode == 27) {\n                        //ESC\n                        dialog.close();\n                    } else if (e.keyCode == 13) {\n                        // ENTER\n                        inner(); // save\n                    } else if (e.keyCode != 13) {\n                        dialog.modified();\n                        return;\n                    }\n                    e.preventDefault();\n                    e.stopPropagation();\n                });\n            }\n        }\n        if (input) input.focus();\n\n        var button = dialog.querySelector(\"button\");\n        button.addEventListener(\"click\", inner);\n\n        function inner() {\n            setValue(input.value);\n        }\n\n        function setValue(value) {\n\n\t\t\tif(info && info.values && info.values.constructor === Object && info.values[value] != undefined )\n\t\t\t\tvalue = info.values[value];\n\n            if (typeof node.properties[property] == \"number\") {\n                value = Number(value);\n            }\n            if (type == \"array\" || type == \"object\") {\n                value = JSON.parse(value);\n            }\n            node.properties[property] = value;\n            if (node.graph) {\n                node.graph._version++;\n            }\n            if (node.onPropertyChanged) {\n                node.onPropertyChanged(property, value);\n            }\n\t\t\tif(options.onclose)\n\t\t\t\toptions.onclose();\n            dialog.close();\n            node.setDirtyCanvas(true, true);\n        }\n\n\t\treturn dialog;\n    };\n\n    // TODO refactor, theer are different dialog, some uses createDialog, some dont\n    LGraphCanvas.prototype.createDialog = function(html, options) {\n        var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true };\n        options = Object.assign(def_options, options || {});\n\n        var dialog = document.createElement(\"div\");\n        dialog.className = \"graphdialog\";\n        dialog.innerHTML = html;\n        dialog.is_modified = false;\n\n        var rect = this.canvas.getBoundingClientRect();\n        var offsetx = -20;\n        var offsety = -20;\n        if (rect) {\n            offsetx -= rect.left;\n            offsety -= rect.top;\n        }\n\n        if (options.position) {\n            offsetx += options.position[0];\n            offsety += options.position[1];\n        } else if (options.event) {\n            offsetx += options.event.clientX;\n            offsety += options.event.clientY;\n        } //centered\n        else {\n            offsetx += this.canvas.width * 0.5;\n            offsety += this.canvas.height * 0.5;\n        }\n\n        dialog.style.left = offsetx + \"px\";\n        dialog.style.top = offsety + \"px\";\n\n        this.canvas.parentNode.appendChild(dialog);\n        \n        // acheck for input and use default behaviour: save on enter, close on esc\n        if (options.checkForInput){\n            var aI = [];\n            var focused = false;\n            if (aI = dialog.querySelectorAll(\"input\")){\n                aI.forEach(function(iX) {\n                    iX.addEventListener(\"keydown\",function(e){\n                        dialog.modified();\n                        if (e.keyCode == 27) {\n                            dialog.close();\n                        } else if (e.keyCode != 13) {\n                            return;\n                        }\n                        // set value ?\n                        e.preventDefault();\n                        e.stopPropagation();\n                    });\n                    if (!focused) iX.focus();\n                });\n            }\n        }\n        \n        dialog.modified = function(){\n            dialog.is_modified = true;\n        }\n        dialog.close = function() {\n            if (dialog.parentNode) {\n                dialog.parentNode.removeChild(dialog);\n            }\n        };\n        \n        var dialogCloseTimer = null;\n        var prevent_timeout = false;\n        dialog.addEventListener(\"mouseleave\", function(e) {\n            if (prevent_timeout)\n                return;\n            if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave)\n                if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave)\n                    dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close();\n        });\n        dialog.addEventListener(\"mouseenter\", function(e) {\n            if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave)\n                if(dialogCloseTimer) clearTimeout(dialogCloseTimer);\n        });\n        var selInDia = dialog.querySelectorAll(\"select\");\n        if (selInDia){\n            // if filtering, check focus changed to comboboxes and prevent closing\n            selInDia.forEach(function(selIn) {\n                selIn.addEventListener(\"click\", function(e) {\n                    prevent_timeout++;\n                });\n                selIn.addEventListener(\"blur\", function(e) {\n                   prevent_timeout = 0;\n                });\n                selIn.addEventListener(\"change\", function(e) {\n                    prevent_timeout = -1;\n                });\n            });\n        }\n\n        return dialog;\n    };\n\n\tLGraphCanvas.prototype.createPanel = function(title, options) {\n\t\toptions = options || {};\n\n\t\tvar ref_window = options.window || window;\n\t\tvar root = document.createElement(\"div\");\n\t\troot.className = \"litegraph dialog\";\n\t\troot.innerHTML = \"<div class='dialog-header'><span class='dialog-title'></span></div><div class='dialog-content'></div><div style='display:none;' class='dialog-alt-content'></div><div class='dialog-footer'></div>\";\n\t\troot.header = root.querySelector(\".dialog-header\");\n\n\t\tif(options.width)\n\t\t\troot.style.width = options.width + (options.width.constructor === Number ? \"px\" : \"\");\n\t\tif(options.height)\n\t\t\troot.style.height = options.height + (options.height.constructor === Number ? \"px\" : \"\");\n\t\tif(options.closable)\n\t\t{\n\t\t\tvar close = document.createElement(\"span\");\n\t\t\tclose.innerHTML = \"&#10005;\";\n\t\t\tclose.classList.add(\"close\");\n\t\t\tclose.addEventListener(\"click\",function(){\n\t\t\t\troot.close();\n\t\t\t});\n\t\t\troot.header.appendChild(close);\n\t\t}\n\t\troot.title_element = root.querySelector(\".dialog-title\");\n\t\troot.title_element.innerText = title;\n\t\troot.content = root.querySelector(\".dialog-content\");\n        root.alt_content = root.querySelector(\".dialog-alt-content\");\n\t\troot.footer = root.querySelector(\".dialog-footer\");\n\n\t\troot.close = function()\n\t\t{\n\t\t    if (root.onClose && typeof root.onClose == \"function\"){\n\t\t        root.onClose();\n\t\t    }\n            if(root.parentNode)\n\t\t        root.parentNode.removeChild(root);\n\t\t    /* XXX CHECK THIS */\n\t\t    if(this.parentNode){\n\t\t    \tthis.parentNode.removeChild(this);\n\t\t    }\n\t\t    /* XXX this was not working, was fixed with an IF, check this */\n\t\t}\n\n        // function to swap panel content\n        root.toggleAltContent = function(force){\n            if (typeof force != \"undefined\"){\n                var vTo = force ? \"block\" : \"none\";\n                var vAlt = force ? \"none\" : \"block\";\n            }else{\n                var vTo = root.alt_content.style.display != \"block\" ? \"block\" : \"none\";\n                var vAlt = root.alt_content.style.display != \"block\" ? \"none\" : \"block\";\n            }\n            root.alt_content.style.display = vTo;\n            root.content.style.display = vAlt;\n        }\n        \n        root.toggleFooterVisibility = function(force){\n            if (typeof force != \"undefined\"){\n                var vTo = force ? \"block\" : \"none\";\n            }else{\n                var vTo = root.footer.style.display != \"block\" ? \"block\" : \"none\";\n            }\n            root.footer.style.display = vTo;\n        }\n        \n\t\troot.clear = function()\n\t\t{\n\t\t\tthis.content.innerHTML = \"\";\n\t\t}\n\n\t\troot.addHTML = function(code, classname, on_footer)\n\t\t{\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\tif(classname)\n\t\t\t\telem.className = classname;\n\t\t\telem.innerHTML = code;\n\t\t\tif(on_footer)\n\t\t\t\troot.footer.appendChild(elem);\n\t\t\telse\n\t\t\t\troot.content.appendChild(elem);\n\t\t\treturn elem;\n\t\t}\n\n\t\troot.addButton = function( name, callback, options )\n\t\t{\n\t\t\tvar elem = document.createElement(\"button\");\n\t\t\telem.innerText = name;\n\t\t\telem.options = options;\n\t\t\telem.classList.add(\"btn\");\n\t\t\telem.addEventListener(\"click\",callback);\n\t\t\troot.footer.appendChild(elem);\n\t\t\treturn elem;\n\t\t}\n\n\t\troot.addSeparator = function()\n\t\t{\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\telem.className = \"separator\";\n\t\t\troot.content.appendChild(elem);\n\t\t}\n\n\t\troot.addWidget = function( type, name, value, options, callback )\n\t\t{\n\t\t\toptions = options || {};\n\t\t\tvar str_value = String(value);\n\t\t\ttype = type.toLowerCase();\n\t\t\tif(type == \"number\")\n\t\t\t\tstr_value = value.toFixed(3);\n\n\t\t\tvar elem = document.createElement(\"div\");\n\t\t\telem.className = \"property\";\n\t\t\telem.innerHTML = \"<span class='property_name'></span><span class='property_value'></span>\";\n\t\t\telem.querySelector(\".property_name\").innerText = options.label || name;\n\t\t\tvar value_element = elem.querySelector(\".property_value\");\n\t\t\tvalue_element.innerText = str_value;\n\t\t\telem.dataset[\"property\"] = name;\n\t\t\telem.dataset[\"type\"] = options.type || type;\n\t\t\telem.options = options;\n\t\t\telem.value = value;\n\n\t\t\tif( type == \"code\" )\n\t\t\t\telem.addEventListener(\"click\", function(e){ root.inner_showCodePad( this.dataset[\"property\"] ); });\n\t\t\telse if (type == \"boolean\")\n\t\t\t{\n\t\t\t\telem.classList.add(\"boolean\");\n\t\t\t\tif(value)\n\t\t\t\t\telem.classList.add(\"bool-on\");\n\t\t\t\telem.addEventListener(\"click\", function(){ \n\t\t\t\t\t//var v = node.properties[this.dataset[\"property\"]]; \n\t\t\t\t\t//node.setProperty(this.dataset[\"property\"],!v); this.innerText = v ? \"true\" : \"false\"; \n\t\t\t\t\tvar propname = this.dataset[\"property\"];\n\t\t\t\t\tthis.value = !this.value;\n\t\t\t\t\tthis.classList.toggle(\"bool-on\");\n\t\t\t\t\tthis.querySelector(\".property_value\").innerText = this.value ? \"true\" : \"false\";\n\t\t\t\t\tinnerChange(propname, this.value );\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if (type == \"string\" || type == \"number\")\n\t\t\t{\n\t\t\t\tvalue_element.setAttribute(\"contenteditable\",true);\n\t\t\t\tvalue_element.addEventListener(\"keydown\", function(e){ \n\t\t\t\t\tif(e.code == \"Enter\" && (type != \"string\" || !e.shiftKey)) // allow for multiline\n\t\t\t\t\t{\n\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\tthis.blur();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tvalue_element.addEventListener(\"blur\", function(){ \n\t\t\t\t\tvar v = this.innerText;\n\t\t\t\t\tvar propname = this.parentNode.dataset[\"property\"];\n\t\t\t\t\tvar proptype = this.parentNode.dataset[\"type\"];\n\t\t\t\t\tif( proptype == \"number\")\n\t\t\t\t\t\tv = Number(v);\n\t\t\t\t\tinnerChange(propname, v);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse if (type == \"enum\" || type == \"combo\") {\n\t\t\t\tvar str_value = LGraphCanvas.getPropertyPrintableValue( value, options.values );\n\t\t\t\tvalue_element.innerText = str_value;\n\n\t\t\t\tvalue_element.addEventListener(\"click\", function(event){ \n\t\t\t\t\tvar values = options.values || [];\n\t\t\t\t\tvar propname = this.parentNode.dataset[\"property\"];\n\t\t\t\t\tvar elem_that = this;\n\t\t\t\t\tvar menu = new LiteGraph.ContextMenu(values,{\n\t\t\t\t\t\t\tevent: event,\n\t\t\t\t\t\t\tclassName: \"dark\",\n\t\t\t\t\t\t\tcallback: inner_clicked\n\t\t\t\t\t\t},\n\t\t\t\t\t\tref_window);\n\t\t\t\t\tfunction inner_clicked(v, option, event) {\n\t\t\t\t\t\t//node.setProperty(propname,v); \n\t\t\t\t\t\t//graphcanvas.dirty_canvas = true;\n\t\t\t\t\t\telem_that.innerText = v;\n\t\t\t\t\t\tinnerChange(propname,v);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t});\n            }\n\n\t\t\troot.content.appendChild(elem);\n\n\t\t\tfunction innerChange(name, value)\n\t\t\t{\n\t\t\t\t//console.log(\"change\",name,value);\n\t\t\t\t//that.dirty_canvas = true;\n\t\t\t\tif(options.callback)\n\t\t\t\t\toptions.callback(name,value,options);\n\t\t\t\tif(callback)\n\t\t\t\t\tcallback(name,value,options);\n\t\t\t}\n\n\t\t\treturn elem;\n\t\t}\n\n        if (root.onOpen && typeof root.onOpen == \"function\") root.onOpen();\n        \n\t\treturn root;\n\t};\n\n\tLGraphCanvas.getPropertyPrintableValue = function(value, values)\n\t{\n\t\tif(!values)\n\t\t\treturn String(value);\n\n\t\tif(values.constructor === Array)\n\t\t{\n\t\t\treturn String(value);\t\t\t\n\t\t}\n\n\t\tif(values.constructor === Object)\n\t\t{\n\t\t\tvar desc_value = \"\";\n\t\t\tfor(var k in values)\n\t\t\t{\n\t\t\t\tif(values[k] != value)\n\t\t\t\t\tcontinue;\n\t\t\t\tdesc_value = k;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn String(value) + \" (\"+desc_value+\")\";\n\t\t}\n\t}\n\n    LGraphCanvas.prototype.closePanels = function(){\n        var panel = document.querySelector(\"#node-panel\");\n\t\tif(panel)\n\t\t\tpanel.close();\n        var panel = document.querySelector(\"#option-panel\");\n\t\tif(panel)\n\t\t\tpanel.close();\n    }\n    \n    LGraphCanvas.prototype.showShowGraphOptionsPanel = function(refOpts, obEv, refMenu, refMenu2){\n        if(this.constructor && this.constructor.name == \"HTMLDivElement\"){\n            // assume coming from the menu event click\n            if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){\n                console.warn(\"Canvas not found\"); // need a ref to canvas obj\n                /*console.debug(event);\n                console.debug(event.target);*/\n                return;\n            }\n            var graphcanvas = obEv.event.target.lgraphcanvas;\n        }else{\n            // assume called internally\n            var graphcanvas = this;\n        }\n        graphcanvas.closePanels();\n        var ref_window = graphcanvas.getCanvasWindow();\n        panel = graphcanvas.createPanel(\"Options\",{\n                                            closable: true\n                                            ,window: ref_window\n                                            ,onOpen: function(){\n                                                graphcanvas.OPTIONPANEL_IS_OPEN = true;\n                                            }\n                                            ,onClose: function(){\n                                                graphcanvas.OPTIONPANEL_IS_OPEN = false;\n                                                graphcanvas.options_panel = null;\n                                            }\n                                        });\n        graphcanvas.options_panel = panel;\n        panel.id = \"option-panel\";\n\t\tpanel.classList.add(\"settings\");\n        \n        function inner_refresh(){\n            \n            panel.content.innerHTML = \"\"; //clear\n\n            var fUpdate = function(name, value, options){\n                switch(name){\n                    /*case \"Render mode\":\n                        // Case \"\".. \n                        if (options.values && options.key){\n                            var kV = Object.values(options.values).indexOf(value);\n                            if (kV>=0 && options.values[kV]){\n                                console.debug(\"update graph options: \"+options.key+\": \"+kV);\n                                graphcanvas[options.key] = kV;\n                                //console.debug(graphcanvas);\n                                break;\n                            }\n                        }\n                        console.warn(\"unexpected options\");\n                        console.debug(options);\n                        break;*/\n                    default:\n                        //console.debug(\"want to update graph options: \"+name+\": \"+value);\n                        if (options && options.key){\n                            name = options.key;\n                        }\n                        if (options.values){\n                            value = Object.values(options.values).indexOf(value);\n                        }\n                        //console.debug(\"update graph option: \"+name+\": \"+value);\n                        graphcanvas[name] = value;\n                        break;\n                }\n            };\n            \n            // panel.addWidget( \"string\", \"Graph name\", \"\", {}, fUpdate); // implement\n            \n            var aProps = LiteGraph.availableCanvasOptions;\n            aProps.sort();\n            for(var pI in aProps){\n                var pX = aProps[pI];\n                panel.addWidget( \"boolean\", pX, graphcanvas[pX], {key: pX, on: \"True\", off: \"False\"}, fUpdate);\n            }\n            \n            var aLinks = [ graphcanvas.links_render_mode ];\n            panel.addWidget( \"combo\", \"Render mode\", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: \"links_render_mode\", values: LiteGraph.LINK_RENDER_MODES}, fUpdate);\n            \n            panel.addSeparator();\n            \n            panel.footer.innerHTML = \"\"; // clear\n\n\t\t}\n        inner_refresh();\n\n\t\tgraphcanvas.canvas.parentNode.appendChild( panel );\n    }\n    \n    LGraphCanvas.prototype.showShowNodePanel = function( node )\n\t{\n\t\tthis.SELECTED_NODE = node;\n\t\tthis.closePanels();\n\t\tvar ref_window = this.getCanvasWindow();\n        var that = this;\n\t\tvar graphcanvas = this;\n\t\tvar panel = this.createPanel(node.title || \"\",{\n                                                    closable: true\n                                                    ,window: ref_window\n                                                    ,onOpen: function(){\n                                                        graphcanvas.NODEPANEL_IS_OPEN = true;\n                                                    }\n                                                    ,onClose: function(){\n                                                        graphcanvas.NODEPANEL_IS_OPEN = false;\n                                                        graphcanvas.node_panel = null;\n                                                    }\n                                                });\n        graphcanvas.node_panel = panel;\n\t\tpanel.id = \"node-panel\";\n\t\tpanel.node = node;\n\t\tpanel.classList.add(\"settings\");\n\n\t\tfunction inner_refresh()\n\t\t{\n\t\t\tpanel.content.innerHTML = \"\"; //clear\n\t\t\tpanel.addHTML(\"<span class='node_type'>\"+node.type+\"</span><span class='node_desc'>\"+(node.constructor.desc || \"\")+\"</span><span class='separator'></span>\");\n\n\t\t\tpanel.addHTML(\"<h3>Properties</h3>\");\n\n            var fUpdate = function(name,value){\n                            graphcanvas.graph.beforeChange(node);\n                            switch(name){\n                                case \"Title\":\n                                    node.title = value;\n                                    break;\n                                case \"Mode\":\n                                    var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value);\n                                    if (kV>=0 && LiteGraph.NODE_MODES[kV]){\n                                        node.changeMode(kV);\n                                    }else{\n                                        console.warn(\"unexpected mode: \"+value);\n                                    }\n                                    break;\n                                case \"Color\":\n                                    if (LGraphCanvas.node_colors[value]){\n                                        node.color = LGraphCanvas.node_colors[value].color;\n                                        node.bgcolor = LGraphCanvas.node_colors[value].bgcolor;\n                                    }else{\n                                        console.warn(\"unexpected color: \"+value);\n                                    }\n                                    break;\n                                default:\n                                    node.setProperty(name,value);\n                                    break;\n                            }\n                            graphcanvas.graph.afterChange();\n                            graphcanvas.dirty_canvas = true;\n                        };\n            \n            panel.addWidget( \"string\", \"Title\", node.title, {}, fUpdate);\n            \n            panel.addWidget( \"combo\", \"Mode\", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate);\n            \n            var nodeCol = \"\";\n            if (node.color !== undefined){\n                nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; });\n            }\n            \n            panel.addWidget( \"combo\", \"Color\", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate);\n            \n            for(var pName in node.properties)\n\t\t\t{\n\t\t\t\tvar value = node.properties[pName];\n\t\t\t\tvar info = node.getPropertyInfo(pName);\n\t\t\t\tvar type = info.type || \"string\";\n\n\t\t\t\t//in case the user wants control over the side panel widget\n\t\t\t\tif( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tpanel.addWidget( info.widget || info.type, pName, value, info, fUpdate);\n\t\t\t}\n\n\t\t\tpanel.addSeparator();\n\n\t\t\tif(node.onShowCustomPanelInfo)\n\t\t\t\tnode.onShowCustomPanelInfo(panel);\n\n            panel.footer.innerHTML = \"\"; // clear\n\t\t\tpanel.addButton(\"Delete\",function(){\n\t\t\t\tif(node.block_delete)\n\t\t\t\t\treturn;\n\t\t\t\tnode.graph.remove(node);\n\t\t\t\tpanel.close();\n\t\t\t}).classList.add(\"delete\");\n\t\t}\n\n\t\tpanel.inner_showCodePad = function( propname )\n\t\t{\n            panel.classList.remove(\"settings\");\n            panel.classList.add(\"centered\");\n\n            \n\t\t\t/*if(window.CodeFlask) //disabled for now\n\t\t\t{\n\t\t\t\tpanel.content.innerHTML = \"<div class='code'></div>\";\n\t\t\t\tvar flask = new CodeFlask( \"div.code\", { language: 'js' });\n\t\t\t\tflask.updateCode(node.properties[propname]);\n\t\t\t\tflask.onUpdate( function(code) {\n\t\t\t\t\tnode.setProperty(propname, code);\n\t\t\t\t});\n\t\t\t}\n\t\t\telse\n\t\t\t{*/\n\t\t\t\tpanel.alt_content.innerHTML = \"<textarea class='code'></textarea>\";\n\t\t\t\tvar textarea = panel.alt_content.querySelector(\"textarea\");\n                var fDoneWith = function(){\n                    panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = \"block\"; // panel.close();\n                    panel.toggleFooterVisibility(true);\n                    textarea.parentNode.removeChild(textarea);\n                    panel.classList.add(\"settings\");\n                    panel.classList.remove(\"centered\");\n                    inner_refresh();\n                }\n\t\t\t\ttextarea.value = node.properties[propname];\n\t\t\t\ttextarea.addEventListener(\"keydown\", function(e){\n\t\t\t\t\tif(e.code == \"Enter\" && e.ctrlKey )\n\t\t\t\t\t{\n\t\t\t\t\t\tnode.setProperty(propname, textarea.value);\n                        fDoneWith();\n\t\t\t\t\t}\n\t\t\t\t});\n                panel.toggleAltContent(true);\n                panel.toggleFooterVisibility(false);\n\t\t\t\ttextarea.style.height = \"calc(100% - 40px)\";\n\t\t\t/*}*/\n\t\t\tvar assign = panel.addButton( \"Assign\", function(){\n\t\t\t\tnode.setProperty(propname, textarea.value);\n                fDoneWith();\n\t\t\t});\n\t\t\tpanel.alt_content.appendChild(assign); //panel.content.appendChild(assign);\n\t\t\tvar button = panel.addButton( \"Close\", fDoneWith);\n\t\t\tbutton.style.float = \"right\";\n\t\t\tpanel.alt_content.appendChild(button); // panel.content.appendChild(button);\n\t\t}\n\n\t\tinner_refresh();\n\n\t\tthis.canvas.parentNode.appendChild( panel );\n\t}\n\t\n\tLGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node)\n\t{\n\t\tconsole.log(\"showing subgraph properties dialog\");\n\n\t\tvar old_panel = this.canvas.parentNode.querySelector(\".subgraph_dialog\");\n\t\tif(old_panel)\n\t\t\told_panel.close();\n\n\t\tvar panel = this.createPanel(\"Subgraph Inputs\",{closable:true, width: 500});\n\t\tpanel.node = node;\n\t\tpanel.classList.add(\"subgraph_dialog\");\n\n\t\tfunction inner_refresh()\n\t\t{\n\t\t\tpanel.clear();\n\n\t\t\t//show currents\n\t\t\tif(node.inputs)\n\t\t\t\tfor(var i = 0; i < node.inputs.length; ++i)\n\t\t\t\t{\n\t\t\t\t\tvar input = node.inputs[i];\n\t\t\t\t\tif(input.not_subgraph_input)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tvar html = \"<button>&#10005;</button> <span class='bullet_icon'></span><span class='name'></span><span class='type'></span>\";\n\t\t\t\t\tvar elem = panel.addHTML(html,\"subgraph_property\");\n\t\t\t\t\telem.dataset[\"name\"] = input.name;\n\t\t\t\t\telem.dataset[\"slot\"] = i;\n\t\t\t\t\telem.querySelector(\".name\").innerText = input.name;\n\t\t\t\t\telem.querySelector(\".type\").innerText = input.type;\n\t\t\t\t\telem.querySelector(\"button\").addEventListener(\"click\",function(e){\n\t\t\t\t\t\tnode.removeInput( Number( this.parentNode.dataset[\"slot\"] ) );\n\t\t\t\t\t\tinner_refresh();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\n\t\t//add extra\n\t\tvar html = \" + <span class='label'>Name</span><input class='name'/><span class='label'>Type</span><input class='type'></input><button>+</button>\";\n\t\tvar elem = panel.addHTML(html,\"subgraph_property extra\", true);\n\t\telem.querySelector(\"button\").addEventListener(\"click\", function(e){\n\t\t\tvar elem = this.parentNode;\n\t\t\tvar name = elem.querySelector(\".name\").value;\n\t\t\tvar type = elem.querySelector(\".type\").value;\n\t\t\tif(!name || node.findInputSlot(name) != -1)\n\t\t\t\treturn;\n\t\t\tnode.addInput(name,type);\n\t\t\telem.querySelector(\".name\").value = \"\";\n\t\t\telem.querySelector(\".type\").value = \"\";\n\t\t\tinner_refresh();\n\t\t});\n\n\t\tinner_refresh();\n\t    this.canvas.parentNode.appendChild(panel);\n\t\treturn panel;\n\t}\n    LGraphCanvas.prototype.showSubgraphPropertiesDialogRight = function (node) {\n\n        // console.log(\"showing subgraph properties dialog\");\n        var that = this;\n        // old_panel if old_panel is exist close it\n        var old_panel = this.canvas.parentNode.querySelector(\".subgraph_dialog\");\n        if (old_panel)\n            old_panel.close();\n        // new panel\n        var panel = this.createPanel(\"Subgraph Outputs\", { closable: true, width: 500 });\n        panel.node = node;\n        panel.classList.add(\"subgraph_dialog\");\n\n        function inner_refresh() {\n            panel.clear();\n            //show currents\n            if (node.outputs)\n                for (var i = 0; i < node.outputs.length; ++i) {\n                    var input = node.outputs[i];\n                    if (input.not_subgraph_output)\n                        continue;\n                    var html = \"<button>&#10005;</button> <span class='bullet_icon'></span><span class='name'></span><span class='type'></span>\";\n                    var elem = panel.addHTML(html, \"subgraph_property\");\n                    elem.dataset[\"name\"] = input.name;\n                    elem.dataset[\"slot\"] = i;\n                    elem.querySelector(\".name\").innerText = input.name;\n                    elem.querySelector(\".type\").innerText = input.type;\n                    elem.querySelector(\"button\").addEventListener(\"click\", function (e) {\n                        node.removeOutput(Number(this.parentNode.dataset[\"slot\"]));\n                        inner_refresh();\n                    });\n                }\n        }\n\n        //add extra\n        var html = \" + <span class='label'>Name</span><input class='name'/><span class='label'>Type</span><input class='type'></input><button>+</button>\";\n        var elem = panel.addHTML(html, \"subgraph_property extra\", true);\n        elem.querySelector(\".name\").addEventListener(\"keydown\", function (e) {\n            if (e.keyCode == 13) {\n                addOutput.apply(this)\n            }\n        })\n        elem.querySelector(\"button\").addEventListener(\"click\", function (e) {\n            addOutput.apply(this)\n        });\n        function addOutput() {\n            var elem = this.parentNode;\n            var name = elem.querySelector(\".name\").value;\n            var type = elem.querySelector(\".type\").value;\n            if (!name || node.findOutputSlot(name) != -1)\n                return;\n            node.addOutput(name, type);\n            elem.querySelector(\".name\").value = \"\";\n            elem.querySelector(\".type\").value = \"\";\n            inner_refresh();\n        }\n\n        inner_refresh();\n        this.canvas.parentNode.appendChild(panel);\n        return panel;\n    }\n\tLGraphCanvas.prototype.checkPanels = function()\n\t{\n\t\tif(!this.canvas)\n\t\t\treturn;\n\t\tvar panels = this.canvas.parentNode.querySelectorAll(\".litegraph.dialog\");\n\t\tfor(var i = 0; i < panels.length; ++i)\n\t\t{\n\t\t\tvar panel = panels[i];\n\t\t\tif( !panel.node )\n\t\t\t\tcontinue;\n\t\t\tif( !panel.node.graph || panel.graph != this.graph )\n\t\t\t\tpanel.close();\n\t\t}\n\t}\n\n    LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) {\n\t\tnode.graph.beforeChange(/*?*/);\n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tnode.collapse();\n\t\t}\n\t\t\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tnode.graph.afterChange(/*?*/);\n    };\n\n    LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) {\n        node.pin();\n    };\n\n    LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) {\n        new LiteGraph.ContextMenu(\n            LiteGraph.NODE_MODES,\n            { event: e, callback: inner_clicked, parentMenu: menu, node: node }\n        );\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n            var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v);\n            var fApplyMultiNode = function(node){\n\t\t\t\tif (kV>=0 && LiteGraph.NODE_MODES[kV])\n\t\t\t\t\tnode.changeMode(kV);\n\t\t\t\telse{\n\t\t\t\t\tconsole.warn(\"unexpected mode: \"+v);\n\t\t\t\t\tnode.changeMode(LiteGraph.ALWAYS);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyMultiNode(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node for color\";\n        }\n\n        var values = [];\n        values.push({\n            value: null,\n            content:\n                \"<span style='display: block; padding-left: 4px;'>No color</span>\"\n        });\n\n        for (var i in LGraphCanvas.node_colors) {\n            var color = LGraphCanvas.node_colors[i];\n            var value = {\n                value: i,\n                content:\n                    \"<span style='display: block; color: #999; padding-left: 4px; border-left: 8px solid \" +\n                    color.color +\n                    \"; background-color:\" +\n                    color.bgcolor +\n                    \"'>\" +\n                    i +\n                    \"</span>\"\n            };\n            values.push(value);\n        }\n        new LiteGraph.ContextMenu(values, {\n            event: e,\n            callback: inner_clicked,\n            parentMenu: menu,\n            node: node\n        });\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n\n            var color = v.value ? LGraphCanvas.node_colors[v.value] : null;\n\t\t\t\n\t\t\tvar fApplyColor = function(node){\n\t\t\t\tif (color) {\n\t\t\t\t\tif (node.constructor === LiteGraph.LGraphGroup) {\n\t\t\t\t\t\tnode.color = color.groupcolor;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnode.color = color.color;\n\t\t\t\t\t\tnode.bgcolor = color.bgcolor;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tdelete node.color;\n\t\t\t\t\tdelete node.bgcolor;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyColor(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyColor(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n            node.setDirtyCanvas(true, true);\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node passed\";\n        }\n\n        new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, {\n            event: e,\n            callback: inner_clicked,\n            parentMenu: menu,\n            node: node\n        });\n\n        function inner_clicked(v) {\n            if (!node) {\n                return;\n            }\n\t\t\tnode.graph.beforeChange(/*?*/); //node\n            \n\t\t\tvar fApplyMultiNode = function(node){\n\t\t\t\tnode.shape = v;\n\t\t\t}\n\n\t\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\t\tfApplyMultiNode(node);\n\t\t\t}else{\n\t\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tnode.graph.afterChange(/*?*/); //node\n            node.setDirtyCanvas(true);\n        }\n\n        return false;\n    };\n\n    LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) {\n        if (!node) {\n            throw \"no node passed\";\n        }\n\n\t\tvar graph = node.graph;\n\t\tgraph.beforeChange();\n        \n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tif (node.removable === false) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tgraph.remove(node);\n\t\t}\n\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tgraph.afterChange();\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) {\n\t\tvar graph = node.graph;\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif(!graphcanvas) //??\n\t\t\treturn;\n\n\t\tvar nodes_list = Object.values( graphcanvas.selected_nodes || {} );\n\t\tif( !nodes_list.length )\n\t\t\tnodes_list = [ node ];\n\n\t\tvar subgraph_node = LiteGraph.createNode(\"graph/subgraph\");\n\t\tsubgraph_node.pos = node.pos.concat();\n\t\tgraph.add(subgraph_node);\n\n\t\tsubgraph_node.buildFromNodes( nodes_list );\n\n\t\tgraphcanvas.deselectAllNodes();\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) {\n        \n\t\tnode.graph.beforeChange();\n        \n\t\tvar newSelected = {};\n\t\t\n\t\tvar fApplyMultiNode = function(node){\n\t\t\tif (node.clonable === false) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar newnode = node.clone();\n\t\t\tif (!newnode) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tnewnode.pos = [node.pos[0] + 5, node.pos[1] + 5];\n\t\t\tnode.graph.add(newnode);\n\t\t\tnewSelected[newnode.id] = newnode;\n\t\t}\n\n\t\tvar graphcanvas = LGraphCanvas.active_canvas;\n\t\tif (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){\n\t\t\tfApplyMultiNode(node);\n\t\t}else{\n\t\t\tfor (var i in graphcanvas.selected_nodes) {\n\t\t\t\tfApplyMultiNode(graphcanvas.selected_nodes[i]);\n\t\t\t}\n\t\t}\n\t\t\n\t\tif(Object.keys(newSelected).length){\n\t\t\tgraphcanvas.selectNodes(newSelected);\n\t\t}\n\t\t\n\t\tnode.graph.afterChange();\n\n        node.setDirtyCanvas(true, true);\n    };\n\n    LGraphCanvas.node_colors = {\n        red: { color: \"#322\", bgcolor: \"#533\", groupcolor: \"#A88\" },\n        brown: { color: \"#332922\", bgcolor: \"#593930\", groupcolor: \"#b06634\" },\n        green: { color: \"#232\", bgcolor: \"#353\", groupcolor: \"#8A8\" },\n        blue: { color: \"#223\", bgcolor: \"#335\", groupcolor: \"#88A\" },\n        pale_blue: {\n            color: \"#2a363b\",\n            bgcolor: \"#3f5159\",\n            groupcolor: \"#3f789e\"\n        },\n        cyan: { color: \"#233\", bgcolor: \"#355\", groupcolor: \"#8AA\" },\n        purple: { color: \"#323\", bgcolor: \"#535\", groupcolor: \"#a1309b\" },\n        yellow: { color: \"#432\", bgcolor: \"#653\", groupcolor: \"#b58b2a\" },\n        black: { color: \"#222\", bgcolor: \"#000\", groupcolor: \"#444\" }\n    };\n\n    LGraphCanvas.prototype.getCanvasMenuOptions = function() {\n        var options = null;\n\t\tvar that = this;\n        if (this.getMenuOptions) {\n            options = this.getMenuOptions();\n        } else {\n            options = [\n                {\n                    content: \"Add Node\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuAdd\n                },\n                { content: \"Add Group\", callback: LGraphCanvas.onGroupAdd },\n\t\t\t\t//{ content: \"Arrange\", callback: that.graph.arrange },\n                //{content:\"Collapse All\", callback: LGraphCanvas.onMenuCollapseAll }\n            ];\n            /*if (LiteGraph.showCanvasOptions){\n                options.push({ content: \"Options\", callback: that.showShowGraphOptionsPanel });\n            }*/\n\n            if (Object.keys(this.selected_nodes).length > 1) {\n                options.push({\n                    content: \"Align\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onGroupAlign,\n                })\n            }\n\n            if (this._graph_stack && this._graph_stack.length > 0) {\n                options.push(null, {\n                    content: \"Close subgraph\",\n                    callback: this.closeSubgraph.bind(this)\n                });\n            }\n        }\n\n        if (this.getExtraMenuOptions) {\n            var extra = this.getExtraMenuOptions(this, options);\n            if (extra) {\n                options = options.concat(extra);\n            }\n        }\n\n        return options;\n    };\n\n    //called by processContextMenu to extract the menu list\n    LGraphCanvas.prototype.getNodeMenuOptions = function(node) {\n        var options = null;\n\n        if (node.getMenuOptions) {\n            options = node.getMenuOptions(this);\n        } else {\n            options = [\n                {\n                    content: \"Inputs\",\n                    has_submenu: true,\n                    disabled: true,\n                    callback: LGraphCanvas.showMenuNodeOptionalInputs\n                },\n                {\n                    content: \"Outputs\",\n                    has_submenu: true,\n                    disabled: true,\n                    callback: LGraphCanvas.showMenuNodeOptionalOutputs\n                },\n                null,\n                {\n                    content: \"Properties\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onShowMenuNodeProperties\n                },\n                null,\n                {\n                    content: \"Title\",\n                    callback: LGraphCanvas.onShowPropertyEditor\n                },\n                {\n                    content: \"Mode\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeMode\n                }];\n            if(node.resizable !== false){\n                options.push({\n                    content: \"Resize\", callback: LGraphCanvas.onMenuResizeNode\n                });\n            }\n            options.push(\n                {\n                    content: \"Collapse\",\n                    callback: LGraphCanvas.onMenuNodeCollapse\n                },\n                { content: \"Pin\", callback: LGraphCanvas.onMenuNodePin },\n                {\n                    content: \"Colors\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeColors\n                },\n                {\n                    content: \"Shapes\",\n                    has_submenu: true,\n                    callback: LGraphCanvas.onMenuNodeShapes\n                },\n                null\n            );\n        }\n\n        if (node.onGetInputs) {\n            var inputs = node.onGetInputs();\n            if (inputs && inputs.length) {\n                options[0].disabled = false;\n            }\n        }\n\n        if (node.onGetOutputs) {\n            var outputs = node.onGetOutputs();\n            if (outputs && outputs.length) {\n                options[1].disabled = false;\n            }\n        }\n\n        if (node.getExtraMenuOptions) {\n            var extra = node.getExtraMenuOptions(this, options);\n            if (extra) {\n                extra.push(null);\n                options = extra.concat(options);\n            }\n        }\n\n        if (node.clonable !== false) {\n            options.push({\n                content: \"Clone\",\n                callback: LGraphCanvas.onMenuNodeClone\n            });\n        }\n\n\t\tif(0) //TODO\n\t\toptions.push({\n\t\t\tcontent: \"To Subgraph\",\n\t\t\tcallback: LGraphCanvas.onMenuNodeToSubgraph\n\t\t});\n\n        if (Object.keys(this.selected_nodes).length > 1) {\n            options.push({\n                content: \"Align Selected To\",\n                has_submenu: true,\n                callback: LGraphCanvas.onNodeAlign,\n            })\n        }\n\n\t\toptions.push(null, {\n\t\t\tcontent: \"Remove\",\n\t\t\tdisabled: !(node.removable !== false && !node.block_delete ),\n\t\t\tcallback: LGraphCanvas.onMenuNodeRemove\n\t\t});\n\n        if (node.graph && node.graph.onGetNodeMenuOptions) {\n            node.graph.onGetNodeMenuOptions(options, node);\n        }\n\n        return options;\n    };\n\n    LGraphCanvas.prototype.getGroupMenuOptions = function(node) {\n        var o = [\n            { content: \"Title\", callback: LGraphCanvas.onShowPropertyEditor },\n            {\n                content: \"Color\",\n                has_submenu: true,\n                callback: LGraphCanvas.onMenuNodeColors\n            },\n            {\n                content: \"Font size\",\n                property: \"font_size\",\n                type: \"Number\",\n                callback: LGraphCanvas.onShowPropertyEditor\n            },\n            null,\n            { content: \"Remove\", callback: LGraphCanvas.onMenuNodeRemove }\n        ];\n\n        return o;\n    };\n\n    LGraphCanvas.prototype.processContextMenu = function(node, event) {\n        var that = this;\n        var canvas = LGraphCanvas.active_canvas;\n        var ref_window = canvas.getCanvasWindow();\n\n        var menu_info = null;\n        var options = {\n            event: event,\n            callback: inner_option_clicked,\n            extra: node\n        };\n\n\t\tif(node)\n\t\t\toptions.title = node.type;\n\n        //check if mouse is in input\n        var slot = null;\n        if (node) {\n            slot = node.getSlotInPosition(event.canvasX, event.canvasY);\n            LGraphCanvas.active_node = node;\n        }\n\n        if (slot) {\n            //on slot\n            menu_info = [];\n            if (node.getSlotMenuOptions) {\n                menu_info = node.getSlotMenuOptions(slot);\n            } else {\n                if (\n                    slot &&\n                    slot.output &&\n                    slot.output.links &&\n                    slot.output.links.length\n                ) {\n                    menu_info.push({ content: \"Disconnect Links\", slot: slot });\n                }\n                var _slot = slot.input || slot.output;\n                if (_slot.removable){\n                \tmenu_info.push(\n\t                    _slot.locked\n\t                        ? \"Cannot remove\"\n\t                        : { content: \"Remove Slot\", slot: slot }\n\t                );\n            \t}\n                if (!_slot.nameLocked){\n\t                menu_info.push({ content: \"Rename Slot\", slot: slot });\n                }\n    \n            }\n            options.title =\n                (slot.input ? slot.input.type : slot.output.type) || \"*\";\n            if (slot.input && slot.input.type == LiteGraph.ACTION) {\n                options.title = \"Action\";\n            }\n            if (slot.output && slot.output.type == LiteGraph.EVENT) {\n                options.title = \"Event\";\n            }\n        } else {\n            if (node) {\n                //on node\n                menu_info = this.getNodeMenuOptions(node);\n            } else {\n                menu_info = this.getCanvasMenuOptions();\n                var group = this.graph.getGroupOnPos(\n                    event.canvasX,\n                    event.canvasY\n                );\n                if (group) {\n                    //on group\n                    menu_info.push(null, {\n                        content: \"Edit Group\",\n                        has_submenu: true,\n                        submenu: {\n                            title: \"Group\",\n                            extra: group,\n                            options: this.getGroupMenuOptions(group)\n                        }\n                    });\n                }\n            }\n        }\n\n        //show menu\n        if (!menu_info) {\n            return;\n        }\n\n        var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window);\n\n        function inner_option_clicked(v, options, e) {\n            if (!v) {\n                return;\n            }\n\n            if (v.content == \"Remove Slot\") {\n                var info = v.slot;\n                node.graph.beforeChange();\n                if (info.input) {\n                    node.removeInput(info.slot);\n                } else if (info.output) {\n                    node.removeOutput(info.slot);\n                }\n                node.graph.afterChange();\n                return;\n            } else if (v.content == \"Disconnect Links\") {\n                var info = v.slot;\n                node.graph.beforeChange();\n                if (info.output) {\n                    node.disconnectOutput(info.slot);\n                } else if (info.input) {\n                    node.disconnectInput(info.slot);\n                }\n                node.graph.afterChange();\n                return;\n            } else if (v.content == \"Rename Slot\") {\n                var info = v.slot;\n                var slot_info = info.input\n                    ? node.getInputInfo(info.slot)\n                    : node.getOutputInfo(info.slot);\n                var dialog = that.createDialog(\n                    \"<span class='name'>Name</span><input autofocus type='text'/><button>OK</button>\",\n                    options\n                );\n                var input = dialog.querySelector(\"input\");\n                if (input && slot_info) {\n                    input.value = slot_info.label || \"\";\n                }\n                var inner = function(){\n                \tnode.graph.beforeChange();\n                    if (input.value) {\n                        if (slot_info) {\n                            slot_info.label = input.value;\n                        }\n                        that.setDirty(true);\n                    }\n                    dialog.close();\n                    node.graph.afterChange();\n                }\n                dialog.querySelector(\"button\").addEventListener(\"click\", inner);\n                input.addEventListener(\"keydown\", function(e) {\n                    dialog.is_modified = true;\n                    if (e.keyCode == 27) {\n                        //ESC\n                        dialog.close();\n                    } else if (e.keyCode == 13) {\n                        inner(); // save\n                    } else if (e.keyCode != 13 && e.target.localName != \"textarea\") {\n                        return;\n                    }\n                    e.preventDefault();\n                    e.stopPropagation();\n                });\n                input.focus();\n            }\n\n            //if(v.callback)\n            //\treturn v.callback.call(that, node, options, e, menu, that, event );\n        }\n    };\n\n    //API *************************************************\n    function compareObjects(a, b) {\n        for (var i in a) {\n            if (a[i] != b[i]) {\n                return false;\n            }\n        }\n        return true;\n    }\n    LiteGraph.compareObjects = compareObjects;\n\n    function distance(a, b) {\n        return Math.sqrt(\n            (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])\n        );\n    }\n    LiteGraph.distance = distance;\n\n    function colorToString(c) {\n        return (\n            \"rgba(\" +\n            Math.round(c[0] * 255).toFixed() +\n            \",\" +\n            Math.round(c[1] * 255).toFixed() +\n            \",\" +\n            Math.round(c[2] * 255).toFixed() +\n            \",\" +\n            (c.length == 4 ? c[3].toFixed(2) : \"1.0\") +\n            \")\"\n        );\n    }\n    LiteGraph.colorToString = colorToString;\n\n    function isInsideRectangle(x, y, left, top, width, height) {\n        if (left < x && left + width > x && top < y && top + height > y) {\n            return true;\n        }\n        return false;\n    }\n    LiteGraph.isInsideRectangle = isInsideRectangle;\n\n    //[minx,miny,maxx,maxy]\n    function growBounding(bounding, x, y) {\n        if (x < bounding[0]) {\n            bounding[0] = x;\n        } else if (x > bounding[2]) {\n            bounding[2] = x;\n        }\n\n        if (y < bounding[1]) {\n            bounding[1] = y;\n        } else if (y > bounding[3]) {\n            bounding[3] = y;\n        }\n    }\n    LiteGraph.growBounding = growBounding;\n\n    //point inside bounding box\n    function isInsideBounding(p, bb) {\n        if (\n            p[0] < bb[0][0] ||\n            p[1] < bb[0][1] ||\n            p[0] > bb[1][0] ||\n            p[1] > bb[1][1]\n        ) {\n            return false;\n        }\n        return true;\n    }\n    LiteGraph.isInsideBounding = isInsideBounding;\n\n    //bounding overlap, format: [ startx, starty, width, height ]\n    function overlapBounding(a, b) {\n        var A_end_x = a[0] + a[2];\n        var A_end_y = a[1] + a[3];\n        var B_end_x = b[0] + b[2];\n        var B_end_y = b[1] + b[3];\n\n        if (\n            a[0] > B_end_x ||\n            a[1] > B_end_y ||\n            A_end_x < b[0] ||\n            A_end_y < b[1]\n        ) {\n            return false;\n        }\n        return true;\n    }\n    LiteGraph.overlapBounding = overlapBounding;\n\n    //Convert a hex value to its decimal value - the inputted hex must be in the\n    //\tformat of a hex triplet - the kind we use for HTML colours. The function\n    //\twill return an array with three values.\n    function hex2num(hex) {\n        if (hex.charAt(0) == \"#\") {\n            hex = hex.slice(1);\n        } //Remove the '#' char - if there is one.\n        hex = hex.toUpperCase();\n        var hex_alphabets = \"0123456789ABCDEF\";\n        var value = new Array(3);\n        var k = 0;\n        var int1, int2;\n        for (var i = 0; i < 6; i += 2) {\n            int1 = hex_alphabets.indexOf(hex.charAt(i));\n            int2 = hex_alphabets.indexOf(hex.charAt(i + 1));\n            value[k] = int1 * 16 + int2;\n            k++;\n        }\n        return value;\n    }\n\n    LiteGraph.hex2num = hex2num;\n\n    //Give a array with three values as the argument and the function will return\n    //\tthe corresponding hex triplet.\n    function num2hex(triplet) {\n        var hex_alphabets = \"0123456789ABCDEF\";\n        var hex = \"#\";\n        var int1, int2;\n        for (var i = 0; i < 3; i++) {\n            int1 = triplet[i] / 16;\n            int2 = triplet[i] % 16;\n\n            hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2);\n        }\n        return hex;\n    }\n\n    LiteGraph.num2hex = num2hex;\n\n    /* LiteGraph GUI elements used for canvas editing *************************************/\n\n    /**\n     * ContextMenu from LiteGUI\n     *\n     * @class ContextMenu\n     * @constructor\n     * @param {Array} values (allows object { title: \"Nice text\", callback: function ... })\n     * @param {Object} options [optional] Some options:\\\n     * - title: title to show on top of the menu\n     * - callback: function to call when an option is clicked, it receives the item information\n     * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback\n     * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position\n     */\n    function ContextMenu(values, options) {\n        options = options || {};\n        this.options = options;\n        var that = this;\n\n        //to link a menu with its parent\n        if (options.parentMenu) {\n            if (options.parentMenu.constructor !== this.constructor) {\n                console.error(\n                    \"parentMenu must be of class ContextMenu, ignoring it\"\n                );\n                options.parentMenu = null;\n            } else {\n                this.parentMenu = options.parentMenu;\n                this.parentMenu.lock = true;\n                this.parentMenu.current_submenu = this;\n            }\n        }\n\n\t\tvar eventClass = null;\n\t\tif(options.event) //use strings because comparing classes between windows doesnt work\n\t\t\teventClass = options.event.constructor.name;\n        if ( eventClass !== \"MouseEvent\" &&\n            eventClass !== \"CustomEvent\" &&\n\t\t\teventClass !== \"PointerEvent\"\n        ) {\n            console.error(\n                \"Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. (\"+eventClass+\")\"\n            );\n            options.event = null;\n        }\n\n        var root = document.createElement(\"div\");\n        root.className = \"litegraph litecontextmenu litemenubar-panel\";\n        if (options.className) {\n            root.className += \" \" + options.className;\n        }\n        root.style.minWidth = 100;\n        root.style.minHeight = 100;\n        root.style.pointerEvents = \"none\";\n        setTimeout(function() {\n            root.style.pointerEvents = \"auto\";\n        }, 100); //delay so the mouse up event is not caught by this element\n\n        //this prevents the default context browser menu to open in case this menu was created when pressing right button\n\t\tLiteGraph.pointerListenerAdd(root,\"up\",\n            function(e) {\n\t\t\t  \t//console.log(\"pointerevents: ContextMenu up root prevent\");\n                e.preventDefault();\n                return true;\n            },\n            true\n        );\n        root.addEventListener(\n            \"contextmenu\",\n            function(e) {\n                if (e.button != 2) {\n                    //right button\n                    return false;\n                }\n                e.preventDefault();\n                return false;\n            },\n            true\n        );\n\n        LiteGraph.pointerListenerAdd(root,\"down\",\n            function(e) {\n\t\t\t  \t//console.log(\"pointerevents: ContextMenu down\");\n                if (e.button == 2) {\n                    that.close();\n                    e.preventDefault();\n                    return true;\n                }\n            },\n            true\n        );\n\n        function on_mouse_wheel(e) {\n            var pos = parseInt(root.style.top);\n            root.style.top =\n                (pos + e.deltaY * options.scroll_speed).toFixed() + \"px\";\n            e.preventDefault();\n            return true;\n        }\n\n        if (!options.scroll_speed) {\n            options.scroll_speed = 0.1;\n        }\n\n        root.addEventListener(\"wheel\", on_mouse_wheel, true);\n        root.addEventListener(\"mousewheel\", on_mouse_wheel, true);\n\n        this.root = root;\n\n        //title\n        if (options.title) {\n            var element = document.createElement(\"div\");\n            element.className = \"litemenu-title\";\n            element.innerHTML = options.title;\n            root.appendChild(element);\n        }\n\n        //entries\n        var num = 0;\n        for (var i=0; i < values.length; i++) {\n            var name = values.constructor == Array ? values[i] : i;\n            if (name != null && name.constructor !== String) {\n                name = name.content === undefined ? String(name) : name.content;\n            }\n            var value = values[i];\n            this.addItem(name, value, options);\n            num++;\n        }\n\n        //close on leave? touch enabled devices won't work TODO use a global device detector and condition on that\n        /*LiteGraph.pointerListenerAdd(root,\"leave\", function(e) {\n\t\t  \tconsole.log(\"pointerevents: ContextMenu leave\");\n            if (that.lock) {\n                return;\n            }\n            if (root.closing_timer) {\n                clearTimeout(root.closing_timer);\n            }\n            root.closing_timer = setTimeout(that.close.bind(that, e), 500);\n            //that.close(e);\n        });*/\n\n\t\tLiteGraph.pointerListenerAdd(root,\"enter\", function(e) {\n\t\t  \t//console.log(\"pointerevents: ContextMenu enter\");\n            if (root.closing_timer) {\n                clearTimeout(root.closing_timer);\n            }\n        });\n\n        //insert before checking position\n        var root_document = document;\n        if (options.event) {\n            root_document = options.event.target.ownerDocument;\n        }\n\n        if (!root_document) {\n            root_document = document;\n        }\n\n\t\tif( root_document.fullscreenElement )\n\t        root_document.fullscreenElement.appendChild(root);\n\t\telse\n\t\t    root_document.body.appendChild(root);\n\n        //compute best position\n        var left = options.left || 0;\n        var top = options.top || 0;\n        if (options.event) {\n            left = options.event.clientX - 10;\n            top = options.event.clientY - 10;\n            if (options.title) {\n                top -= 20;\n            }\n\n            if (options.parentMenu) {\n                var rect = options.parentMenu.root.getBoundingClientRect();\n                left = rect.left + rect.width;\n            }\n\n            var body_rect = document.body.getBoundingClientRect();\n            var root_rect = root.getBoundingClientRect();\n\t\t\tif(body_rect.height == 0)\n\t\t\t\tconsole.error(\"document.body height is 0. That is dangerous, set html,body { height: 100%; }\");\n\n            if (body_rect.width && left > body_rect.width - root_rect.width - 10) {\n                left = body_rect.width - root_rect.width - 10;\n            }\n            if (body_rect.height && top > body_rect.height - root_rect.height - 10) {\n                top = body_rect.height - root_rect.height - 10;\n            }\n        }\n\n        root.style.left = left + \"px\";\n        root.style.top = top + \"px\";\n\n        if (options.scale) {\n            root.style.transform = \"scale(\" + options.scale + \")\";\n        }\n    }\n\n    ContextMenu.prototype.addItem = function(name, value, options) {\n        var that = this;\n        options = options || {};\n\n        var element = document.createElement(\"div\");\n        element.className = \"litemenu-entry submenu\";\n\n        var disabled = false;\n\n        if (value === null) {\n            element.classList.add(\"separator\");\n            //element.innerHTML = \"<hr/>\"\n            //continue;\n        } else {\n            element.innerHTML = value && value.title ? value.title : name;\n            element.value = value;\n\n            if (value) {\n                if (value.disabled) {\n                    disabled = true;\n                    element.classList.add(\"disabled\");\n                }\n                if (value.submenu || value.has_submenu) {\n                    element.classList.add(\"has_submenu\");\n                }\n            }\n\n            if (typeof value == \"function\") {\n                element.dataset[\"value\"] = name;\n                element.onclick_callback = value;\n            } else {\n                element.dataset[\"value\"] = value;\n            }\n\n            if (value.className) {\n                element.className += \" \" + value.className;\n            }\n        }\n\n        this.root.appendChild(element);\n        if (!disabled) {\n            element.addEventListener(\"click\", inner_onclick);\n        }\n        if (!disabled && options.autoopen) {\n\t\t\tLiteGraph.pointerListenerAdd(element,\"enter\",inner_over);\n        }\n\n        function inner_over(e) {\n            var value = this.value;\n            if (!value || !value.has_submenu) {\n                return;\n            }\n            //if it is a submenu, autoopen like the item was clicked\n            inner_onclick.call(this, e);\n        }\n\n        //menu option clicked\n        function inner_onclick(e) {\n            var value = this.value;\n            var close_parent = true;\n\n            if (that.current_submenu) {\n                that.current_submenu.close(e);\n            }\n\n            //global callback\n            if (options.callback) {\n                var r = options.callback.call(\n                    this,\n                    value,\n                    options,\n                    e,\n                    that,\n                    options.node\n                );\n                if (r === true) {\n                    close_parent = false;\n                }\n            }\n\n            //special cases\n            if (value) {\n                if (\n                    value.callback &&\n                    !options.ignore_item_callbacks &&\n                    value.disabled !== true\n                ) {\n                    //item callback\n                    var r = value.callback.call(\n                        this,\n                        value,\n                        options,\n                        e,\n                        that,\n                        options.extra\n                    );\n                    if (r === true) {\n                        close_parent = false;\n                    }\n                }\n                if (value.submenu) {\n                    if (!value.submenu.options) {\n                        throw \"ContextMenu submenu needs options\";\n                    }\n                    var submenu = new that.constructor(value.submenu.options, {\n                        callback: value.submenu.callback,\n                        event: e,\n                        parentMenu: that,\n                        ignore_item_callbacks:\n                            value.submenu.ignore_item_callbacks,\n                        title: value.submenu.title,\n                        extra: value.submenu.extra,\n                        autoopen: options.autoopen\n                    });\n                    close_parent = false;\n                }\n            }\n\n            if (close_parent && !that.lock) {\n                that.close();\n            }\n        }\n\n        return element;\n    };\n\n    ContextMenu.prototype.close = function(e, ignore_parent_menu) {\n        if (this.root.parentNode) {\n            this.root.parentNode.removeChild(this.root);\n        }\n        if (this.parentMenu && !ignore_parent_menu) {\n            this.parentMenu.lock = false;\n            this.parentMenu.current_submenu = null;\n            if (e === undefined) {\n                this.parentMenu.close();\n            } else if (\n                e &&\n                !ContextMenu.isCursorOverElement(e, this.parentMenu.root)\n            ) {\n                ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method+\"leave\", e);\n            }\n        }\n        if (this.current_submenu) {\n            this.current_submenu.close(e, true);\n        }\n\n        if (this.root.closing_timer) {\n            clearTimeout(this.root.closing_timer);\n        }\n        \n        // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu\n        // on key press, allow filtering/selecting the context menu elements\n    };\n\n    //this code is used to trigger events easily (used in the context menu mouseleave\n    ContextMenu.trigger = function(element, event_name, params, origin) {\n        var evt = document.createEvent(\"CustomEvent\");\n        evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail\n        evt.srcElement = origin;\n        if (element.dispatchEvent) {\n            element.dispatchEvent(evt);\n        } else if (element.__events) {\n            element.__events.dispatchEvent(evt);\n        }\n        //else nothing seems binded here so nothing to do\n        return evt;\n    };\n\n    //returns the top most menu\n    ContextMenu.prototype.getTopMenu = function() {\n        if (this.options.parentMenu) {\n            return this.options.parentMenu.getTopMenu();\n        }\n        return this;\n    };\n\n    ContextMenu.prototype.getFirstEvent = function() {\n        if (this.options.parentMenu) {\n            return this.options.parentMenu.getFirstEvent();\n        }\n        return this.options.event;\n    };\n\n    ContextMenu.isCursorOverElement = function(event, element) {\n        var left = event.clientX;\n        var top = event.clientY;\n        var rect = element.getBoundingClientRect();\n        if (!rect) {\n            return false;\n        }\n        if (\n            top > rect.top &&\n            top < rect.top + rect.height &&\n            left > rect.left &&\n            left < rect.left + rect.width\n        ) {\n            return true;\n        }\n        return false;\n    };\n\n    LiteGraph.ContextMenu = ContextMenu;\n\n    LiteGraph.closeAllContextMenus = function(ref_window) {\n        ref_window = ref_window || window;\n\n        var elements = ref_window.document.querySelectorAll(\".litecontextmenu\");\n        if (!elements.length) {\n            return;\n        }\n\n        var result = [];\n        for (var i = 0; i < elements.length; i++) {\n            result.push(elements[i]);\n        }\n\n        for (var i=0; i < result.length; i++) {\n            if (result[i].close) {\n                result[i].close();\n            } else if (result[i].parentNode) {\n                result[i].parentNode.removeChild(result[i]);\n            }\n        }\n    };\n\n    LiteGraph.extendClass = function(target, origin) {\n        for (var i in origin) {\n            //copy class properties\n            if (target.hasOwnProperty(i)) {\n                continue;\n            }\n            target[i] = origin[i];\n        }\n\n        if (origin.prototype) {\n            //copy prototype properties\n            for (var i in origin.prototype) {\n                //only enumerable\n                if (!origin.prototype.hasOwnProperty(i)) {\n                    continue;\n                }\n\n                if (target.prototype.hasOwnProperty(i)) {\n                    //avoid overwriting existing ones\n                    continue;\n                }\n\n                //copy getters\n                if (origin.prototype.__lookupGetter__(i)) {\n                    target.prototype.__defineGetter__(\n                        i,\n                        origin.prototype.__lookupGetter__(i)\n                    );\n                } else {\n                    target.prototype[i] = origin.prototype[i];\n                }\n\n                //and setters\n                if (origin.prototype.__lookupSetter__(i)) {\n                    target.prototype.__defineSetter__(\n                        i,\n                        origin.prototype.__lookupSetter__(i)\n                    );\n                }\n            }\n        }\n    };\n\n\t//used by some widgets to render a curve editor\n\tfunction CurveEditor( points )\n\t{\n\t\tthis.points = points;\n\t\tthis.selected = -1;\n\t\tthis.nearest = -1;\n\t\tthis.size = null; //stores last size used\n\t\tthis.must_update = true;\n\t\tthis.margin = 5;\n\t}\n\n\tCurveEditor.sampleCurve = function(f,points)\n\t{\n\t\tif(!points)\n\t\t\treturn;\n\t\tfor(var i = 0; i < points.length - 1; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tvar pn = points[i+1];\n\t\t\tif(pn[0] < f)\n\t\t\t\tcontinue;\n\t\t\tvar r = (pn[0] - p[0]);\n\t\t\tif( Math.abs(r) < 0.00001 )\n\t\t\t\treturn p[1];\n\t\t\tvar local_f = (f - p[0]) / r;\n\t\t\treturn p[1] * (1.0 - local_f) + pn[1] * local_f;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tCurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tthis.size = size;\n\t\tvar w = size[0] - this.margin * 2;\n\t\tvar h = size[1] - this.margin * 2;\n\n\t\tline_color = line_color || \"#666\";\n\n\t\tctx.save();\n\t\tctx.translate(this.margin,this.margin);\n\n\t\tif(background_color)\n\t\t{\n\t\t\tctx.fillStyle = \"#111\";\n\t\t\tctx.fillRect(0,0,w,h);\n\t\t\tctx.fillStyle = \"#222\";\n\t\t\tctx.fillRect(w*0.5,0,1,h);\n\t\t\tctx.strokeStyle = \"#333\";\n\t\t\tctx.strokeRect(0,0,w,h);\n\t\t}\n\t\tctx.strokeStyle = line_color;\n\t\tif(inactive)\n\t\t\tctx.globalAlpha = 0.5;\n\t\tctx.beginPath();\n\t\tfor(var i = 0; i < points.length; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tctx.lineTo( p[0] * w, (1.0 - p[1]) * h );\n\t\t}\n\t\tctx.stroke();\n\t\tctx.globalAlpha = 1;\n\t\tif(!inactive)\n\t\t\tfor(var i = 0; i < points.length; ++i)\n\t\t\t{\n\t\t\t\tvar p = points[i];\n\t\t\t\tctx.fillStyle = this.selected == i ? \"#FFF\" : (this.nearest == i ? \"#DDD\" : \"#AAA\");\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 );\n\t\t\t\tctx.fill();\n\t\t\t}\n\t\tctx.restore();\n\t}\n\n\t//localpos is mouse in curve editor space\n\tCurveEditor.prototype.onMouseDown = function( localpos, graphcanvas )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tif( localpos[1] < 0 )\n\t\t\treturn;\n\n\t\t//this.captureInput(true);\n\t\tvar w = this.size[0] - this.margin * 2;\n\t\tvar h = this.size[1] - this.margin * 2;\n\t\tvar x = localpos[0] - this.margin;\n\t\tvar y = localpos[1] - this.margin;\n\t\tvar pos = [x,y];\n\t\tvar max_dist = 30 / graphcanvas.ds.scale;\n\t\t//search closer one\n\t\tthis.selected = this.getCloserPoint(pos, max_dist);\n\t\t//create one\n\t\tif(this.selected == -1)\n\t\t{\n\t\t\tvar point = [x / w, 1 - y / h];\n\t\t\tpoints.push(point);\n\t\t\tpoints.sort(function(a,b){ return a[0] - b[0]; });\n\t\t\tthis.selected = points.indexOf(point);\n\t\t\tthis.must_update = true;\n\t\t}\n\t\tif(this.selected != -1)\n\t\t\treturn true;\n\t}\n\n\tCurveEditor.prototype.onMouseMove = function( localpos, graphcanvas )\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn;\n\t\tvar s = this.selected;\n\t\tif(s < 0)\n\t\t\treturn;\n\t\tvar x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 );\n\t\tvar y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 );\n\t\tvar curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)];\n\t\tvar max_dist = 30 / graphcanvas.ds.scale;\n\t\tthis._nearest = this.getCloserPoint(curvepos, max_dist);\n\t\tvar point = points[s];\n\t\tif(point)\n\t\t{\n\t\t\tvar is_edge_point = s == 0 || s == points.length - 1;\n\t\t\tif( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) )\n\t\t\t{\n\t\t\t\tpoints.splice(s,1);\n\t\t\t\tthis.selected = -1;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif( !is_edge_point ) //not edges\n\t\t\t\tpoint[0] = clamp(x, 0, 1);\n\t\t\telse\n\t\t\t\tpoint[0] = s == 0 ? 0 : 1;\n\t\t\tpoint[1] = 1.0 - clamp(y, 0, 1);\n\t\t\tpoints.sort(function(a,b){ return a[0] - b[0]; });\n\t\t\tthis.selected = points.indexOf(point);\n\t\t\tthis.must_update = true;\n\t\t}\n\t}\n\n\tCurveEditor.prototype.onMouseUp = function( localpos, graphcanvas )\n\t{\n\t\tthis.selected = -1;\n\t\treturn false;\n\t}\n\n\tCurveEditor.prototype.getCloserPoint = function(pos, max_dist)\n\t{\n\t\tvar points = this.points;\n\t\tif(!points)\n\t\t\treturn -1;\n\t\tmax_dist = max_dist || 30;\n\t\tvar w = (this.size[0] - this.margin * 2);\n\t\tvar h = (this.size[1] - this.margin * 2);\n\t\tvar num = points.length;\n\t\tvar p2 = [0,0];\n\t\tvar min_dist = 1000000;\n\t\tvar closest = -1;\n\t\tvar last_valid = -1;\n\t\tfor(var i = 0; i < num; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tp2[0] = p[0] * w;\n\t\t\tp2[1] = (1.0 - p[1]) * h;\n\t\t\tif(p2[0] < pos[0])\n\t\t\t\tlast_valid = i;\n\t\t\tvar dist = vec2.distance(pos,p2);\n\t\t\tif(dist > min_dist || dist > max_dist)\n\t\t\t\tcontinue;\n\t\t\tclosest = i;\n\t\t\tmin_dist = dist;\n\t\t}\n\t\treturn closest;\n\t}\n\n\tLiteGraph.CurveEditor = CurveEditor;\n\n    //used to create nodes from wrapping functions\n    LiteGraph.getParameterNames = function(func) {\n        return (func + \"\")\n            .replace(/[/][/].*$/gm, \"\") // strip single-line comments\n            .replace(/\\s+/g, \"\") // strip white space\n            .replace(/[/][*][^/*]*[*][/]/g, \"\") // strip multi-line comments  /**/\n            .split(\"){\", 1)[0]\n            .replace(/^[^(]*[(]/, \"\") // extract the parameters\n            .replace(/=[^,]+/g, \"\") // strip any ES6 defaults\n            .split(\",\")\n            .filter(Boolean); // split & filter [\"\"]\n    };\n\n\t/* helper for interaction: pointer, touch, mouse Listeners\n\tused by LGraphCanvas DragAndScale ContextMenu*/\n\tLiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) {\n\t\tif (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!==\"function\"){\n\t\t\t//console.log(\"cant pointerListenerAdd \"+oDOM+\", \"+sEvent+\", \"+fCall);\n\t\t\treturn; // -- break --\n\t\t}\n\t\t\n\t\tvar sMethod = LiteGraph.pointerevents_method;\n\t\tvar sEvent = sEvIn;\n\t\t\n\t\t// UNDER CONSTRUCTION\n\t\t// convert pointerevents to touch event when not available\n\t\tif (sMethod==\"pointer\" && !window.PointerEvent){ \n\t\t\tconsole.warn(\"sMethod=='pointer' && !window.PointerEvent\");\n\t\t\tconsole.log(\"Converting pointer[\"+sEvent+\"] : down move up cancel enter TO touchstart touchmove touchend, etc ..\");\n\t\t\tswitch(sEvent){\n\t\t\t\tcase \"down\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\tsEvent = \"start\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"move\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\t//sEvent = \"move\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"up\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\tsEvent = \"end\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"cancel\":{\n\t\t\t\t\tsMethod = \"touch\";\n\t\t\t\t\t//sEvent = \"cancel\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"enter\":{\n\t\t\t\t\tconsole.log(\"debug: Should I send a move event?\"); // ???\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// case \"over\": case \"out\": not used at now\n\t\t\t\tdefault:{\n\t\t\t\t\tconsole.warn(\"PointerEvent not available in this browser ? The event \"+sEvent+\" would not be called\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tswitch(sEvent){\n\t\t\t//both pointer and move events\n\t\t\tcase \"down\": case \"up\": case \"move\": case \"over\": case \"out\": case \"enter\":\n\t\t\t{\n\t\t\t\toDOM.addEventListener(sMethod+sEvent, fCall, capture);\n\t\t\t}\n\t\t\t// only pointerevents\n\t\t\tcase \"leave\": case \"cancel\": case \"gotpointercapture\": case \"lostpointercapture\":\n\t\t\t{\n\t\t\t\tif (sMethod!=\"mouse\"){\n\t\t\t\t\treturn oDOM.addEventListener(sMethod+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// not \"pointer\" || \"mouse\"\n\t\t\tdefault:\n\t\t\t\treturn oDOM.addEventListener(sEvent, fCall, capture);\n\t\t}\n\t}\n\tLiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) {\n\t\tif (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!==\"function\"){\n\t\t\t//console.log(\"cant pointerListenerRemove \"+oDOM+\", \"+sEvent+\", \"+fCall);\n\t\t\treturn; // -- break --\n\t\t}\n\t\tswitch(sEvent){\n\t\t\t//both pointer and move events\n\t\t\tcase \"down\": case \"up\": case \"move\": case \"over\": case \"out\": case \"enter\":\n\t\t\t{\n\t\t\t\tif (LiteGraph.pointerevents_method==\"pointer\" || LiteGraph.pointerevents_method==\"mouse\"){\n\t\t\t\t\toDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// only pointerevents\n\t\t\tcase \"leave\": case \"cancel\": case \"gotpointercapture\": case \"lostpointercapture\":\n\t\t\t{\n\t\t\t\tif (LiteGraph.pointerevents_method==\"pointer\"){\n\t\t\t\t\treturn oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// not \"pointer\" || \"mouse\"\n\t\t\tdefault:\n\t\t\t\treturn oDOM.removeEventListener(sEvent, fCall, capture);\n\t\t}\n\t}\n\n    function clamp(v, a, b) {\n        return a > v ? a : b < v ? b : v;\n    };\n    global.clamp = clamp;\n\n    if (typeof window != \"undefined\" && !window[\"requestAnimationFrame\"]) {\n        window.requestAnimationFrame =\n            window.webkitRequestAnimationFrame ||\n            window.mozRequestAnimationFrame ||\n            function(callback) {\n                window.setTimeout(callback, 1000 / 60);\n            };\n    }\n})(this);\n\nif (typeof exports != \"undefined\") {\n    exports.LiteGraph = this.LiteGraph;\n    exports.LGraph = this.LGraph;\n    exports.LLink = this.LLink;\n    exports.LGraphNode = this.LGraphNode;\n    exports.LGraphGroup = this.LGraphGroup;\n    exports.DragAndScale = this.DragAndScale;\n    exports.LGraphCanvas = this.LGraphCanvas;\n    exports.ContextMenu = this.ContextMenu;\n}\n\n"
  },
  {
    "path": "src/litegraph.test.js",
    "content": "describe(\"register node types\", () => {\n    let lg;\n    let Sum;\n\n    beforeEach(() => {\n        jest.resetModules();\n        lg = require(\"./litegraph\");\n        Sum = function Sum() {\n            this.addInput(\"a\", \"number\");\n            this.addInput(\"b\", \"number\");\n            this.addOutput(\"sum\", \"number\");\n        };\n        Sum.prototype.onExecute = function (a, b) {\n            this.setOutputData(0, a + b);\n        };\n    });\n\n    afterEach(() => {\n        jest.restoreAllMocks();\n    });\n\n    test(\"normal case\", () => {\n        lg.LiteGraph.registerNodeType(\"math/sum\", Sum);\n\n        let node = lg.LiteGraph.registered_node_types[\"math/sum\"];\n        expect(node).toBeTruthy();\n        expect(node.type).toBe(\"math/sum\");\n        expect(node.title).toBe(\"Sum\");\n        expect(node.category).toBe(\"math\");\n        expect(node.prototype.configure).toBe(\n            lg.LGraphNode.prototype.configure\n        );\n    });\n\n    test(\"callback triggers\", () => {\n        const consoleSpy = jest\n            .spyOn(console, \"log\")\n            .mockImplementation(() => {});\n\n        lg.LiteGraph.onNodeTypeRegistered = jest.fn();\n        lg.LiteGraph.onNodeTypeReplaced = jest.fn();\n        lg.LiteGraph.registerNodeType(\"math/sum\", Sum);\n        expect(lg.LiteGraph.onNodeTypeRegistered).toHaveBeenCalled();\n        expect(lg.LiteGraph.onNodeTypeReplaced).not.toHaveBeenCalled();\n        lg.LiteGraph.registerNodeType(\"math/sum\", Sum);\n        expect(lg.LiteGraph.onNodeTypeReplaced).toHaveBeenCalled();\n        expect(consoleSpy).toHaveBeenCalledWith(\n            expect.stringMatching(\"replacing node type\")\n        );\n        expect(consoleSpy).toHaveBeenCalledWith(\n            expect.stringMatching(\"math/sum\")\n        );\n    });\n\n    test(\"node with title\", () => {\n        Sum.title = \"The sum title\";\n        lg.LiteGraph.registerNodeType(\"math/sum\", Sum);\n        let node = lg.LiteGraph.registered_node_types[\"math/sum\"];\n        expect(node.title).toBe(\"The sum title\");\n        expect(node.title).not.toBe(node.name);\n    });\n\n    test(\"handle error simple object\", () => {\n        expect(() =>\n            lg.LiteGraph.registerNodeType(\"math/sum\", { simple: \"type\" })\n        ).toThrow(\"Cannot register a simple object\");\n    });\n\n    test(\"check shape mapping\", () => {\n        lg.LiteGraph.registerNodeType(\"math/sum\", Sum);\n\n        const node_type = lg.LiteGraph.registered_node_types[\"math/sum\"];\n        expect(new node_type().shape).toBe(undefined);\n        node_type.prototype.shape = \"default\";\n        expect(new node_type().shape).toBe(undefined);\n        node_type.prototype.shape = \"box\";\n        expect(new node_type().shape).toBe(lg.LiteGraph.BOX_SHAPE);\n        node_type.prototype.shape = \"round\";\n        expect(new node_type().shape).toBe(lg.LiteGraph.ROUND_SHAPE);\n        node_type.prototype.shape = \"circle\";\n        expect(new node_type().shape).toBe(lg.LiteGraph.CIRCLE_SHAPE);\n        node_type.prototype.shape = \"card\";\n        expect(new node_type().shape).toBe(lg.LiteGraph.CARD_SHAPE);\n        node_type.prototype.shape = \"custom_shape\";\n        expect(new node_type().shape).toBe(\"custom_shape\");\n\n        // Check that also works for replaced node types\n        jest.spyOn(console, \"log\").mockImplementation(() => {});\n        function NewCalcSum(a, b) {\n            return a + b;\n        }\n        lg.LiteGraph.registerNodeType(\"math/sum\", NewCalcSum);\n        const new_node_type = lg.LiteGraph.registered_node_types[\"math/sum\"];\n        new_node_type.prototype.shape = \"box\";\n        expect(new new_node_type().shape).toBe(lg.LiteGraph.BOX_SHAPE);\n    });\n\n    test(\"onPropertyChanged warning\", () => {\n        const consoleSpy = jest\n            .spyOn(console, \"warn\")\n            .mockImplementation(() => {});\n\n        Sum.prototype.onPropertyChange = true;\n        lg.LiteGraph.registerNodeType(\"math/sum\", Sum);\n        expect(consoleSpy).toBeCalledTimes(1);\n        expect(consoleSpy).toBeCalledWith(\n            expect.stringContaining(\"has onPropertyChange method\")\n        );\n        expect(consoleSpy).toBeCalledWith(expect.stringContaining(\"math/sum\"));\n    });\n\n    test(\"registering supported file extensions\", () => {\n        expect(lg.LiteGraph.node_types_by_file_extension).toEqual({});\n\n        // Create two node types with calc_times overriding .pdf\n        Sum.supported_extensions = [\"PDF\", \"exe\", null];\n        function Times() {\n            this.addInput(\"a\", \"number\");\n            this.addInput(\"b\", \"number\");\n            this.addOutput(\"times\", \"number\");\n        }\n        Times.prototype.onExecute = function (a, b) {\n            this.setOutputData(0, a * b);\n        };\n        Times.supported_extensions = [\"pdf\", \"jpg\"];\n        lg.LiteGraph.registerNodeType(\"math/sum\", Sum);\n        lg.LiteGraph.registerNodeType(\"math/times\", Times);\n\n        expect(\n            Object.keys(lg.LiteGraph.node_types_by_file_extension).length\n        ).toBe(3);\n        expect(lg.LiteGraph.node_types_by_file_extension).toHaveProperty(\"pdf\");\n        expect(lg.LiteGraph.node_types_by_file_extension).toHaveProperty(\"exe\");\n        expect(lg.LiteGraph.node_types_by_file_extension).toHaveProperty(\"jpg\");\n\n        expect(lg.LiteGraph.node_types_by_file_extension.exe).toBe(Sum);\n        expect(lg.LiteGraph.node_types_by_file_extension.pdf).toBe(Times);\n        expect(lg.LiteGraph.node_types_by_file_extension.jpg).toBe(Times);\n    });\n\n    test(\"register in/out slot types\", () => {\n        expect(lg.LiteGraph.registered_slot_in_types).toEqual({});\n        expect(lg.LiteGraph.registered_slot_out_types).toEqual({});\n\n        // Test slot type registration with first type\n        lg.LiteGraph.auto_load_slot_types = true;\n        lg.LiteGraph.registerNodeType(\"math/sum\", Sum);\n        expect(lg.LiteGraph.registered_slot_in_types).toEqual({\n            number: { nodes: [\"math/sum\"] },\n        });\n        expect(lg.LiteGraph.registered_slot_out_types).toEqual({\n            number: { nodes: [\"math/sum\"] },\n        });\n\n        // Test slot type registration with second type\n        function ToInt() {\n            this.addInput(\"string\", \"string\");\n            this.addOutput(\"number\", \"number\");\n        };\n        ToInt.prototype.onExecute = function (str) {\n            this.setOutputData(0, Number(str));\n        };\n        lg.LiteGraph.registerNodeType(\"basic/to_int\", ToInt);\n        expect(lg.LiteGraph.registered_slot_in_types).toEqual({\n            number: { nodes: [\"math/sum\"] },\n            string: { nodes: [\"basic/to_int\"] },\n        });\n        expect(lg.LiteGraph.registered_slot_out_types).toEqual({\n            number: { nodes: [\"math/sum\", \"basic/to_int\"] },\n        });\n    });\n});\n\ndescribe(\"unregister node types\", () => {\n    let lg;\n    let Sum;\n\n    beforeEach(() => {\n        jest.resetModules();\n        lg = require(\"./litegraph\");\n        Sum = function Sum() {\n            this.addInput(\"a\", \"number\");\n            this.addInput(\"b\", \"number\");\n            this.addOutput(\"sum\", \"number\");\n        };\n        Sum.prototype.onExecute = function (a, b) {\n            this.setOutputData(0, a + b);\n        };\n    });\n\n    afterEach(() => {\n        jest.restoreAllMocks();\n    });\n\n    test(\"remove by name\", () => {\n        lg.LiteGraph.registerNodeType(\"math/sum\", Sum);\n        expect(lg.LiteGraph.registered_node_types[\"math/sum\"]).toBeTruthy();\n\n        lg.LiteGraph.unregisterNodeType(\"math/sum\");\n        expect(lg.LiteGraph.registered_node_types[\"math/sum\"]).toBeFalsy();\n    });\n\n    test(\"remove by object\", () => {\n        lg.LiteGraph.registerNodeType(\"math/sum\", Sum);\n        expect(lg.LiteGraph.registered_node_types[\"math/sum\"]).toBeTruthy();\n\n        lg.LiteGraph.unregisterNodeType(Sum);\n        expect(lg.LiteGraph.registered_node_types[\"math/sum\"]).toBeFalsy();\n    });\n\n    test(\"try removing with wrong name\", () => {\n        expect(() => lg.LiteGraph.unregisterNodeType(\"missing/type\")).toThrow(\n            \"node type not found: missing/type\"\n        );\n    });\n\n    test(\"no constructor name\", () => {\n        function BlankNode() {}\n        BlankNode.constructor = {}\n        lg.LiteGraph.registerNodeType(\"blank/node\", BlankNode);\n        expect(lg.LiteGraph.registered_node_types[\"blank/node\"]).toBeTruthy()\n\n        lg.LiteGraph.unregisterNodeType(\"blank/node\");\n        expect(lg.LiteGraph.registered_node_types[\"blank/node\"]).toBeFalsy();\n    })\n});\n"
  },
  {
    "path": "src/nodes/audio.js",
    "content": "(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    var LGAudio = {};\r\n    global.LGAudio = LGAudio;\r\n\r\n    LGAudio.getAudioContext = function() {\r\n        if (!this._audio_context) {\r\n            window.AudioContext =\r\n                window.AudioContext || window.webkitAudioContext;\r\n            if (!window.AudioContext) {\r\n                console.error(\"AudioContext not supported by browser\");\r\n                return null;\r\n            }\r\n            this._audio_context = new AudioContext();\r\n            this._audio_context.onmessage = function(msg) {\r\n                console.log(\"msg\", msg);\r\n            };\r\n            this._audio_context.onended = function(msg) {\r\n                console.log(\"ended\", msg);\r\n            };\r\n            this._audio_context.oncomplete = function(msg) {\r\n                console.log(\"complete\", msg);\r\n            };\r\n        }\r\n\r\n        //in case it crashes\r\n        //if(this._audio_context.state == \"suspended\")\r\n        //\tthis._audio_context.resume();\r\n        return this._audio_context;\r\n    };\r\n\r\n    LGAudio.connect = function(audionodeA, audionodeB) {\r\n        try {\r\n            audionodeA.connect(audionodeB);\r\n        } catch (err) {\r\n            console.warn(\"LGraphAudio:\", err);\r\n        }\r\n    };\r\n\r\n    LGAudio.disconnect = function(audionodeA, audionodeB) {\r\n        try {\r\n            audionodeA.disconnect(audionodeB);\r\n        } catch (err) {\r\n            console.warn(\"LGraphAudio:\", err);\r\n        }\r\n    };\r\n\r\n    LGAudio.changeAllAudiosConnections = function(node, connect) {\r\n        if (node.inputs) {\r\n            for (var i = 0; i < node.inputs.length; ++i) {\r\n                var input = node.inputs[i];\r\n                var link_info = node.graph.links[input.link];\r\n                if (!link_info) {\r\n                    continue;\r\n                }\r\n\r\n                var origin_node = node.graph.getNodeById(link_info.origin_id);\r\n                var origin_audionode = null;\r\n                if (origin_node.getAudioNodeInOutputSlot) {\r\n                    origin_audionode = origin_node.getAudioNodeInOutputSlot(\r\n                        link_info.origin_slot\r\n                    );\r\n                } else {\r\n                    origin_audionode = origin_node.audionode;\r\n                }\r\n\r\n                var target_audionode = null;\r\n                if (node.getAudioNodeInInputSlot) {\r\n                    target_audionode = node.getAudioNodeInInputSlot(i);\r\n                } else {\r\n                    target_audionode = node.audionode;\r\n                }\r\n\r\n                if (connect) {\r\n                    LGAudio.connect(origin_audionode, target_audionode);\r\n                } else {\r\n                    LGAudio.disconnect(origin_audionode, target_audionode);\r\n                }\r\n            }\r\n        }\r\n\r\n        if (node.outputs) {\r\n            for (var i = 0; i < node.outputs.length; ++i) {\r\n                var output = node.outputs[i];\r\n                for (var j = 0; j < output.links.length; ++j) {\r\n                    var link_info = node.graph.links[output.links[j]];\r\n                    if (!link_info) {\r\n                        continue;\r\n                    }\r\n\r\n                    var origin_audionode = null;\r\n                    if (node.getAudioNodeInOutputSlot) {\r\n                        origin_audionode = node.getAudioNodeInOutputSlot(i);\r\n                    } else {\r\n                        origin_audionode = node.audionode;\r\n                    }\r\n\r\n                    var target_node = node.graph.getNodeById(\r\n                        link_info.target_id\r\n                    );\r\n                    var target_audionode = null;\r\n                    if (target_node.getAudioNodeInInputSlot) {\r\n                        target_audionode = target_node.getAudioNodeInInputSlot(\r\n                            link_info.target_slot\r\n                        );\r\n                    } else {\r\n                        target_audionode = target_node.audionode;\r\n                    }\r\n\r\n                    if (connect) {\r\n                        LGAudio.connect(origin_audionode, target_audionode);\r\n                    } else {\r\n                        LGAudio.disconnect(origin_audionode, target_audionode);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    //used by many nodes\r\n    LGAudio.onConnectionsChange = function(\r\n        connection,\r\n        slot,\r\n        connected,\r\n        link_info\r\n    ) {\r\n        //only process the outputs events\r\n        if (connection != LiteGraph.OUTPUT) {\r\n            return;\r\n        }\r\n\r\n        var target_node = null;\r\n        if (link_info) {\r\n            target_node = this.graph.getNodeById(link_info.target_id);\r\n        }\r\n\r\n        if (!target_node) {\r\n            return;\r\n        }\r\n\r\n        //get origin audionode\r\n        var local_audionode = null;\r\n        if (this.getAudioNodeInOutputSlot) {\r\n            local_audionode = this.getAudioNodeInOutputSlot(slot);\r\n        } else {\r\n            local_audionode = this.audionode;\r\n        }\r\n\r\n        //get target audionode\r\n        var target_audionode = null;\r\n        if (target_node.getAudioNodeInInputSlot) {\r\n            target_audionode = target_node.getAudioNodeInInputSlot(\r\n                link_info.target_slot\r\n            );\r\n        } else {\r\n            target_audionode = target_node.audionode;\r\n        }\r\n\r\n        //do the connection/disconnection\r\n        if (connected) {\r\n            LGAudio.connect(local_audionode, target_audionode);\r\n        } else {\r\n            LGAudio.disconnect(local_audionode, target_audionode);\r\n        }\r\n    };\r\n\r\n    //this function helps creating wrappers to existing classes\r\n    LGAudio.createAudioNodeWrapper = function(class_object) {\r\n        var old_func = class_object.prototype.onPropertyChanged;\r\n\r\n        class_object.prototype.onPropertyChanged = function(name, value) {\r\n            if (old_func) {\r\n                old_func.call(this, name, value);\r\n            }\r\n\r\n            if (!this.audionode) {\r\n                return;\r\n            }\r\n\r\n            if (this.audionode[name] === undefined) {\r\n                return;\r\n            }\r\n\r\n            if (this.audionode[name].value !== undefined) {\r\n                this.audionode[name].value = value;\r\n            } else {\r\n                this.audionode[name] = value;\r\n            }\r\n        };\r\n\r\n        class_object.prototype.onConnectionsChange =\r\n            LGAudio.onConnectionsChange;\r\n    };\r\n\r\n    //contains the samples decoded of the loaded audios in AudioBuffer format\r\n    LGAudio.cached_audios = {};\r\n\r\n    LGAudio.loadSound = function(url, on_complete, on_error) {\r\n        if (LGAudio.cached_audios[url] && url.indexOf(\"blob:\") == -1) {\r\n            if (on_complete) {\r\n                on_complete(LGAudio.cached_audios[url]);\r\n            }\r\n            return;\r\n        }\r\n\r\n        if (LGAudio.onProcessAudioURL) {\r\n            url = LGAudio.onProcessAudioURL(url);\r\n        }\r\n\r\n        //load new sample\r\n        var request = new XMLHttpRequest();\r\n        request.open(\"GET\", url, true);\r\n        request.responseType = \"arraybuffer\";\r\n\r\n        var context = LGAudio.getAudioContext();\r\n\r\n        // Decode asynchronously\r\n        request.onload = function() {\r\n            console.log(\"AudioSource loaded\");\r\n            context.decodeAudioData(\r\n                request.response,\r\n                function(buffer) {\r\n                    console.log(\"AudioSource decoded\");\r\n                    LGAudio.cached_audios[url] = buffer;\r\n                    if (on_complete) {\r\n                        on_complete(buffer);\r\n                    }\r\n                },\r\n                onError\r\n            );\r\n        };\r\n        request.send();\r\n\r\n        function onError(err) {\r\n            console.log(\"Audio loading sample error:\", err);\r\n            if (on_error) {\r\n                on_error(err);\r\n            }\r\n        }\r\n\r\n        return request;\r\n    };\r\n\r\n    //****************************************************\r\n\r\n    function LGAudioSource() {\r\n        this.properties = {\r\n            src: \"\",\r\n            gain: 0.5,\r\n            loop: true,\r\n            autoplay: true,\r\n            playbackRate: 1\r\n        };\r\n\r\n        this._loading_audio = false;\r\n        this._audiobuffer = null; //points to AudioBuffer with the audio samples decoded\r\n        this._audionodes = [];\r\n        this._last_sourcenode = null; //the last AudioBufferSourceNode (there could be more if there are several sounds playing)\r\n\r\n        this.addOutput(\"out\", \"audio\");\r\n        this.addInput(\"gain\", \"number\");\r\n\r\n        //init context\r\n        var context = LGAudio.getAudioContext();\r\n\r\n        //create gain node to control volume\r\n        this.audionode = context.createGain();\r\n        this.audionode.graphnode = this;\r\n        this.audionode.gain.value = this.properties.gain;\r\n\r\n        //debug\r\n        if (this.properties.src) {\r\n            this.loadSound(this.properties.src);\r\n        }\r\n    }\r\n\r\n\tLGAudioSource.desc = \"Plays an audio file\";\r\n    LGAudioSource[\"@src\"] = { widget: \"resource\" };\r\n    LGAudioSource.supported_extensions = [\"wav\", \"ogg\", \"mp3\"];\r\n\r\n    LGAudioSource.prototype.onAdded = function(graph) {\r\n        if (graph.status === LGraph.STATUS_RUNNING) {\r\n            this.onStart();\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.onStart = function() {\r\n        if (!this._audiobuffer) {\r\n            return;\r\n        }\r\n\r\n        if (this.properties.autoplay) {\r\n\t\t\tthis.playBuffer(this._audiobuffer);\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.onStop = function() {\r\n        this.stopAllSounds();\r\n    };\r\n\r\n    LGAudioSource.prototype.onPause = function() {\r\n        this.pauseAllSounds();\r\n    };\r\n\r\n    LGAudioSource.prototype.onUnpause = function() {\r\n        this.unpauseAllSounds();\r\n        //this.onStart();\r\n    };\r\n\r\n    LGAudioSource.prototype.onRemoved = function() {\r\n        this.stopAllSounds();\r\n        if (this._dropped_url) {\r\n            URL.revokeObjectURL(this._url);\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.stopAllSounds = function() {\r\n        //iterate and stop\r\n        for (var i = 0; i < this._audionodes.length; ++i) {\r\n            if (this._audionodes[i].started) {\r\n                this._audionodes[i].started = false;\r\n                this._audionodes[i].stop();\r\n            }\r\n            //this._audionodes[i].disconnect( this.audionode );\r\n        }\r\n        this._audionodes.length = 0;\r\n    };\r\n\r\n    LGAudioSource.prototype.pauseAllSounds = function() {\r\n        LGAudio.getAudioContext().suspend();\r\n    };\r\n\r\n    LGAudioSource.prototype.unpauseAllSounds = function() {\r\n        LGAudio.getAudioContext().resume();\r\n    };\r\n\r\n    LGAudioSource.prototype.onExecute = function() {\r\n        if (this.inputs) {\r\n            for (var i = 0; i < this.inputs.length; ++i) {\r\n                var input = this.inputs[i];\r\n                if (input.link == null) {\r\n                    continue;\r\n                }\r\n                var v = this.getInputData(i);\r\n                if (v === undefined) {\r\n                    continue;\r\n                }\r\n                if (input.name == \"gain\")\r\n                    this.audionode.gain.value = v;\r\n                else if (input.name == \"src\") {\r\n                    this.setProperty(\"src\",v);\r\n                } else if (input.name == \"playbackRate\") {\r\n                    this.properties.playbackRate = v;\r\n                    for (var j = 0; j < this._audionodes.length; ++j) {\r\n                        this._audionodes[j].playbackRate.value = v;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        if (this.outputs) {\r\n            for (var i = 0; i < this.outputs.length; ++i) {\r\n                var output = this.outputs[i];\r\n                if (output.name == \"buffer\" && this._audiobuffer) {\r\n                    this.setOutputData(i, this._audiobuffer);\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.onAction = function(event) {\r\n        if (this._audiobuffer) {\r\n            if (event == \"Play\") {\r\n                this.playBuffer(this._audiobuffer);\r\n            } else if (event == \"Stop\") {\r\n                this.stopAllSounds();\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"src\") {\r\n            this.loadSound(value);\r\n        } else if (name == \"gain\") {\r\n            this.audionode.gain.value = value;\r\n        } else if (name == \"playbackRate\") {\r\n            for (var j = 0; j < this._audionodes.length; ++j) {\r\n                this._audionodes[j].playbackRate.value = value;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioSource.prototype.playBuffer = function(buffer) {\r\n        var that = this;\r\n        var context = LGAudio.getAudioContext();\r\n\r\n        //create a new audionode (this is mandatory, AudioAPI doesnt like to reuse old ones)\r\n        var audionode = context.createBufferSource(); //create a AudioBufferSourceNode\r\n        this._last_sourcenode = audionode;\r\n        audionode.graphnode = this;\r\n        audionode.buffer = buffer;\r\n        audionode.loop = this.properties.loop;\r\n        audionode.playbackRate.value = this.properties.playbackRate;\r\n        this._audionodes.push(audionode);\r\n        audionode.connect(this.audionode); //connect to gain\r\n\r\n\t\tthis._audionodes.push(audionode);\r\n\r\n\t\tthis.trigger(\"start\");\r\n\r\n        audionode.onended = function() {\r\n            //console.log(\"ended!\");\r\n            that.trigger(\"ended\");\r\n            //remove\r\n            var index = that._audionodes.indexOf(audionode);\r\n            if (index != -1) {\r\n                that._audionodes.splice(index, 1);\r\n            }\r\n        };\r\n\r\n        if (!audionode.started) {\r\n            audionode.started = true;\r\n            audionode.start();\r\n        }\r\n        return audionode;\r\n    };\r\n\r\n    LGAudioSource.prototype.loadSound = function(url) {\r\n        var that = this;\r\n\r\n        //kill previous load\r\n        if (this._request) {\r\n            this._request.abort();\r\n            this._request = null;\r\n        }\r\n\r\n        this._audiobuffer = null; //points to the audiobuffer once the audio is loaded\r\n        this._loading_audio = false;\r\n\r\n        if (!url) {\r\n            return;\r\n        }\r\n\r\n        this._request = LGAudio.loadSound(url, inner);\r\n\r\n        this._loading_audio = true;\r\n        this.boxcolor = \"#AA4\";\r\n\r\n        function inner(buffer) {\r\n            this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR;\r\n            that._audiobuffer = buffer;\r\n            that._loading_audio = false;\r\n            //if is playing, then play it\r\n            if (that.graph && that.graph.status === LGraph.STATUS_RUNNING) {\r\n                that.onStart();\r\n            } //this controls the autoplay already\r\n        }\r\n    };\r\n\r\n    //Helps connect/disconnect AudioNodes when new connections are made in the node\r\n    LGAudioSource.prototype.onConnectionsChange = LGAudio.onConnectionsChange;\r\n\r\n    LGAudioSource.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"playbackRate\", \"number\"],\r\n\t\t\t[\"src\",\"string\"],\r\n            [\"Play\", LiteGraph.ACTION],\r\n            [\"Stop\", LiteGraph.ACTION]\r\n        ];\r\n    };\r\n\r\n    LGAudioSource.prototype.onGetOutputs = function() {\r\n        return [[\"buffer\", \"audiobuffer\"], [\"start\", LiteGraph.EVENT], [\"ended\", LiteGraph.EVENT]];\r\n    };\r\n\r\n    LGAudioSource.prototype.onDropFile = function(file) {\r\n        if (this._dropped_url) {\r\n            URL.revokeObjectURL(this._dropped_url);\r\n        }\r\n        var url = URL.createObjectURL(file);\r\n        this.properties.src = url;\r\n        this.loadSound(url);\r\n        this._dropped_url = url;\r\n    };\r\n\r\n    LGAudioSource.title = \"Source\";\r\n    LGAudioSource.desc = \"Plays audio\";\r\n    LiteGraph.registerNodeType(\"audio/source\", LGAudioSource);\r\n\r\n    //****************************************************\r\n\r\n    function LGAudioMediaSource() {\r\n        this.properties = {\r\n            gain: 0.5\r\n        };\r\n\r\n        this._audionodes = [];\r\n        this._media_stream = null;\r\n\r\n        this.addOutput(\"out\", \"audio\");\r\n        this.addInput(\"gain\", \"number\");\r\n\r\n        //create gain node to control volume\r\n        var context = LGAudio.getAudioContext();\r\n        this.audionode = context.createGain();\r\n        this.audionode.graphnode = this;\r\n        this.audionode.gain.value = this.properties.gain;\r\n    }\r\n\r\n    LGAudioMediaSource.prototype.onAdded = function(graph) {\r\n        if (graph.status === LGraph.STATUS_RUNNING) {\r\n            this.onStart();\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onStart = function() {\r\n        if (this._media_stream == null && !this._waiting_confirmation) {\r\n            this.openStream();\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onStop = function() {\r\n        this.audionode.gain.value = 0;\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onPause = function() {\r\n        this.audionode.gain.value = 0;\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onUnpause = function() {\r\n        this.audionode.gain.value = this.properties.gain;\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onRemoved = function() {\r\n        this.audionode.gain.value = 0;\r\n        if (this.audiosource_node) {\r\n            this.audiosource_node.disconnect(this.audionode);\r\n            this.audiosource_node = null;\r\n        }\r\n        if (this._media_stream) {\r\n            var tracks = this._media_stream.getTracks();\r\n            if (tracks.length) {\r\n                tracks[0].stop();\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.openStream = function() {\r\n        if (!navigator.mediaDevices) {\r\n            console.log(\r\n                \"getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags\"\r\n            );\r\n            return;\r\n        }\r\n\r\n        this._waiting_confirmation = true;\r\n\r\n        // Not showing vendor prefixes.\r\n        navigator.mediaDevices\r\n            .getUserMedia({ audio: true, video: false })\r\n            .then(this.streamReady.bind(this))\r\n            .catch(onFailSoHard);\r\n\r\n        var that = this;\r\n        function onFailSoHard(err) {\r\n            console.log(\"Media rejected\", err);\r\n            that._media_stream = false;\r\n            that.boxcolor = \"red\";\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.streamReady = function(localMediaStream) {\r\n        this._media_stream = localMediaStream;\r\n        //this._waiting_confirmation = false;\r\n\r\n        //init context\r\n        if (this.audiosource_node) {\r\n            this.audiosource_node.disconnect(this.audionode);\r\n        }\r\n        var context = LGAudio.getAudioContext();\r\n        this.audiosource_node = context.createMediaStreamSource(\r\n            localMediaStream\r\n        );\r\n        this.audiosource_node.graphnode = this;\r\n        this.audiosource_node.connect(this.audionode);\r\n        this.boxcolor = \"white\";\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onExecute = function() {\r\n        if (this._media_stream == null && !this._waiting_confirmation) {\r\n            this.openStream();\r\n        }\r\n\r\n        if (this.inputs) {\r\n            for (var i = 0; i < this.inputs.length; ++i) {\r\n                var input = this.inputs[i];\r\n                if (input.link == null) {\r\n                    continue;\r\n                }\r\n                var v = this.getInputData(i);\r\n                if (v === undefined) {\r\n                    continue;\r\n                }\r\n                if (input.name == \"gain\") {\r\n                    this.audionode.gain.value = this.properties.gain = v;\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onAction = function(event) {\r\n        if (event == \"Play\") {\r\n            this.audionode.gain.value = this.properties.gain;\r\n        } else if (event == \"Stop\") {\r\n            this.audionode.gain.value = 0;\r\n        }\r\n    };\r\n\r\n    LGAudioMediaSource.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"gain\") {\r\n            this.audionode.gain.value = value;\r\n        }\r\n    };\r\n\r\n    //Helps connect/disconnect AudioNodes when new connections are made in the node\r\n    LGAudioMediaSource.prototype.onConnectionsChange =\r\n        LGAudio.onConnectionsChange;\r\n\r\n    LGAudioMediaSource.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"playbackRate\", \"number\"],\r\n            [\"Play\", LiteGraph.ACTION],\r\n            [\"Stop\", LiteGraph.ACTION]\r\n        ];\r\n    };\r\n\r\n    LGAudioMediaSource.title = \"MediaSource\";\r\n    LGAudioMediaSource.desc = \"Plays microphone\";\r\n    LiteGraph.registerNodeType(\"audio/media_source\", LGAudioMediaSource);\r\n\r\n    //*****************************************************\r\n\r\n    function LGAudioAnalyser() {\r\n        this.properties = {\r\n            fftSize: 2048,\r\n            minDecibels: -100,\r\n            maxDecibels: -10,\r\n            smoothingTimeConstant: 0.5\r\n        };\r\n\r\n        var context = LGAudio.getAudioContext();\r\n\r\n        this.audionode = context.createAnalyser();\r\n        this.audionode.graphnode = this;\r\n        this.audionode.fftSize = this.properties.fftSize;\r\n        this.audionode.minDecibels = this.properties.minDecibels;\r\n        this.audionode.maxDecibels = this.properties.maxDecibels;\r\n        this.audionode.smoothingTimeConstant = this.properties.smoothingTimeConstant;\r\n\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addOutput(\"freqs\", \"array\");\r\n        this.addOutput(\"samples\", \"array\");\r\n\r\n        this._freq_bin = null;\r\n        this._time_bin = null;\r\n    }\r\n\r\n    LGAudioAnalyser.prototype.onPropertyChanged = function(name, value) {\r\n        this.audionode[name] = value;\r\n    };\r\n\r\n    LGAudioAnalyser.prototype.onExecute = function() {\r\n        if (this.isOutputConnected(0)) {\r\n            //send FFT\r\n            var bufferLength = this.audionode.frequencyBinCount;\r\n            if (!this._freq_bin || this._freq_bin.length != bufferLength) {\r\n                this._freq_bin = new Uint8Array(bufferLength);\r\n            }\r\n            this.audionode.getByteFrequencyData(this._freq_bin);\r\n            this.setOutputData(0, this._freq_bin);\r\n        }\r\n\r\n        //send analyzer\r\n        if (this.isOutputConnected(1)) {\r\n            //send Samples\r\n            var bufferLength = this.audionode.frequencyBinCount;\r\n            if (!this._time_bin || this._time_bin.length != bufferLength) {\r\n                this._time_bin = new Uint8Array(bufferLength);\r\n            }\r\n            this.audionode.getByteTimeDomainData(this._time_bin);\r\n            this.setOutputData(1, this._time_bin);\r\n        }\r\n\r\n        //properties\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n            if (input.link == null) {\r\n                continue;\r\n            }\r\n            var v = this.getInputData(i);\r\n            if (v !== undefined) {\r\n                this.audionode[input.name].value = v;\r\n            }\r\n        }\r\n\r\n        //time domain\r\n        //this.audionode.getFloatTimeDomainData( dataArray );\r\n    };\r\n\r\n    LGAudioAnalyser.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"minDecibels\", \"number\"],\r\n            [\"maxDecibels\", \"number\"],\r\n            [\"smoothingTimeConstant\", \"number\"]\r\n        ];\r\n    };\r\n\r\n    LGAudioAnalyser.prototype.onGetOutputs = function() {\r\n        return [[\"freqs\", \"array\"], [\"samples\", \"array\"]];\r\n    };\r\n\r\n    LGAudioAnalyser.title = \"Analyser\";\r\n    LGAudioAnalyser.desc = \"Audio Analyser\";\r\n    LiteGraph.registerNodeType(\"audio/analyser\", LGAudioAnalyser);\r\n\r\n    //*****************************************************\r\n\r\n    function LGAudioGain() {\r\n        //default\r\n        this.properties = {\r\n            gain: 1\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createGain();\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addInput(\"gain\", \"number\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioGain.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n            var v = this.getInputData(i);\r\n            if (v !== undefined) {\r\n                this.audionode[input.name].value = v;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioGain);\r\n\r\n    LGAudioGain.title = \"Gain\";\r\n    LGAudioGain.desc = \"Audio gain\";\r\n    LiteGraph.registerNodeType(\"audio/gain\", LGAudioGain);\r\n\r\n    function LGAudioConvolver() {\r\n        //default\r\n        this.properties = {\r\n            impulse_src: \"\",\r\n            normalize: true\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createConvolver();\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioConvolver);\r\n\r\n    LGAudioConvolver.prototype.onRemove = function() {\r\n        if (this._dropped_url) {\r\n            URL.revokeObjectURL(this._dropped_url);\r\n        }\r\n    };\r\n\r\n    LGAudioConvolver.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"impulse_src\") {\r\n            this.loadImpulse(value);\r\n        } else if (name == \"normalize\") {\r\n            this.audionode.normalize = value;\r\n        }\r\n    };\r\n\r\n    LGAudioConvolver.prototype.onDropFile = function(file) {\r\n        if (this._dropped_url) {\r\n            URL.revokeObjectURL(this._dropped_url);\r\n        }\r\n        this._dropped_url = URL.createObjectURL(file);\r\n        this.properties.impulse_src = this._dropped_url;\r\n        this.loadImpulse(this._dropped_url);\r\n    };\r\n\r\n    LGAudioConvolver.prototype.loadImpulse = function(url) {\r\n        var that = this;\r\n\r\n        //kill previous load\r\n        if (this._request) {\r\n            this._request.abort();\r\n            this._request = null;\r\n        }\r\n\r\n        this._impulse_buffer = null;\r\n        this._loading_impulse = false;\r\n\r\n        if (!url) {\r\n            return;\r\n        }\r\n\r\n        //load new sample\r\n        this._request = LGAudio.loadSound(url, inner);\r\n        this._loading_impulse = true;\r\n\r\n        // Decode asynchronously\r\n        function inner(buffer) {\r\n            that._impulse_buffer = buffer;\r\n            that.audionode.buffer = buffer;\r\n            console.log(\"Impulse signal set\");\r\n            that._loading_impulse = false;\r\n        }\r\n    };\r\n\r\n    LGAudioConvolver.title = \"Convolver\";\r\n    LGAudioConvolver.desc = \"Convolves the signal (used for reverb)\";\r\n    LiteGraph.registerNodeType(\"audio/convolver\", LGAudioConvolver);\r\n\r\n    function LGAudioDynamicsCompressor() {\r\n        //default\r\n        this.properties = {\r\n            threshold: -50,\r\n            knee: 40,\r\n            ratio: 12,\r\n            reduction: -20,\r\n            attack: 0,\r\n            release: 0.25\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createDynamicsCompressor();\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioDynamicsCompressor);\r\n\r\n    LGAudioDynamicsCompressor.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n            if (input.link == null) {\r\n                continue;\r\n            }\r\n            var v = this.getInputData(i);\r\n            if (v !== undefined) {\r\n                this.audionode[input.name].value = v;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioDynamicsCompressor.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"threshold\", \"number\"],\r\n            [\"knee\", \"number\"],\r\n            [\"ratio\", \"number\"],\r\n            [\"reduction\", \"number\"],\r\n            [\"attack\", \"number\"],\r\n            [\"release\", \"number\"]\r\n        ];\r\n    };\r\n\r\n    LGAudioDynamicsCompressor.title = \"DynamicsCompressor\";\r\n    LGAudioDynamicsCompressor.desc = \"Dynamics Compressor\";\r\n    LiteGraph.registerNodeType(\r\n        \"audio/dynamicsCompressor\",\r\n        LGAudioDynamicsCompressor\r\n    );\r\n\r\n    function LGAudioWaveShaper() {\r\n        //default\r\n        this.properties = {};\r\n\r\n        this.audionode = LGAudio.getAudioContext().createWaveShaper();\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addInput(\"shape\", \"waveshape\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioWaveShaper.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n        var v = this.getInputData(1);\r\n        if (v === undefined) {\r\n            return;\r\n        }\r\n        this.audionode.curve = v;\r\n    };\r\n\r\n    LGAudioWaveShaper.prototype.setWaveShape = function(shape) {\r\n        this.audionode.curve = shape;\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioWaveShaper);\r\n\r\n    /* disabled till I dont find a way to do a wave shape\r\nLGAudioWaveShaper.title = \"WaveShaper\";\r\nLGAudioWaveShaper.desc = \"Distortion using wave shape\";\r\nLiteGraph.registerNodeType(\"audio/waveShaper\", LGAudioWaveShaper);\r\n*/\r\n\r\n    function LGAudioMixer() {\r\n        //default\r\n        this.properties = {\r\n            gain1: 0.5,\r\n            gain2: 0.5\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createGain();\r\n\r\n        this.audionode1 = LGAudio.getAudioContext().createGain();\r\n        this.audionode1.gain.value = this.properties.gain1;\r\n        this.audionode2 = LGAudio.getAudioContext().createGain();\r\n        this.audionode2.gain.value = this.properties.gain2;\r\n\r\n        this.audionode1.connect(this.audionode);\r\n        this.audionode2.connect(this.audionode);\r\n\r\n        this.addInput(\"in1\", \"audio\");\r\n        this.addInput(\"in1 gain\", \"number\");\r\n        this.addInput(\"in2\", \"audio\");\r\n        this.addInput(\"in2 gain\", \"number\");\r\n\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioMixer.prototype.getAudioNodeInInputSlot = function(slot) {\r\n        if (slot == 0) {\r\n            return this.audionode1;\r\n        } else if (slot == 2) {\r\n            return this.audionode2;\r\n        }\r\n    };\r\n\r\n    LGAudioMixer.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"gain1\") {\r\n            this.audionode1.gain.value = value;\r\n        } else if (name == \"gain2\") {\r\n            this.audionode2.gain.value = value;\r\n        }\r\n    };\r\n\r\n    LGAudioMixer.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n\r\n            if (input.link == null || input.type == \"audio\") {\r\n                continue;\r\n            }\r\n\r\n            var v = this.getInputData(i);\r\n            if (v === undefined) {\r\n                continue;\r\n            }\r\n\r\n            if (i == 1) {\r\n                this.audionode1.gain.value = v;\r\n            } else if (i == 3) {\r\n                this.audionode2.gain.value = v;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioMixer);\r\n\r\n    LGAudioMixer.title = \"Mixer\";\r\n    LGAudioMixer.desc = \"Audio mixer\";\r\n    LiteGraph.registerNodeType(\"audio/mixer\", LGAudioMixer);\r\n\r\n    function LGAudioADSR() {\r\n        //default\r\n        this.properties = {\r\n            A: 0.1,\r\n            D: 0.1,\r\n            S: 0.1,\r\n            R: 0.1\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createGain();\r\n        this.audionode.gain.value = 0;\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addInput(\"gate\", \"boolean\");\r\n        this.addOutput(\"out\", \"audio\");\r\n        this.gate = false;\r\n    }\r\n\r\n    LGAudioADSR.prototype.onExecute = function() {\r\n        var audioContext = LGAudio.getAudioContext();\r\n        var now = audioContext.currentTime;\r\n        var node = this.audionode;\r\n        var gain = node.gain;\r\n        var current_gate = this.getInputData(1);\r\n\r\n        var A = this.getInputOrProperty(\"A\");\r\n        var D = this.getInputOrProperty(\"D\");\r\n        var S = this.getInputOrProperty(\"S\");\r\n        var R = this.getInputOrProperty(\"R\");\r\n\r\n        if (!this.gate && current_gate) {\r\n            gain.cancelScheduledValues(0);\r\n            gain.setValueAtTime(0, now);\r\n            gain.linearRampToValueAtTime(1, now + A);\r\n            gain.linearRampToValueAtTime(S, now + A + D);\r\n        } else if (this.gate && !current_gate) {\r\n            gain.cancelScheduledValues(0);\r\n            gain.setValueAtTime(gain.value, now);\r\n            gain.linearRampToValueAtTime(0, now + R);\r\n        }\r\n\r\n        this.gate = current_gate;\r\n    };\r\n\r\n    LGAudioADSR.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"A\", \"number\"],\r\n            [\"D\", \"number\"],\r\n            [\"S\", \"number\"],\r\n            [\"R\", \"number\"]\r\n        ];\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioADSR);\r\n\r\n    LGAudioADSR.title = \"ADSR\";\r\n    LGAudioADSR.desc = \"Audio envelope\";\r\n    LiteGraph.registerNodeType(\"audio/adsr\", LGAudioADSR);\r\n\r\n    function LGAudioDelay() {\r\n        //default\r\n        this.properties = {\r\n            delayTime: 0.5\r\n        };\r\n\r\n        this.audionode = LGAudio.getAudioContext().createDelay(10);\r\n        this.audionode.delayTime.value = this.properties.delayTime;\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addInput(\"time\", \"number\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioDelay);\r\n\r\n    LGAudioDelay.prototype.onExecute = function() {\r\n        var v = this.getInputData(1);\r\n        if (v !== undefined) {\r\n            this.audionode.delayTime.value = v;\r\n        }\r\n    };\r\n\r\n    LGAudioDelay.title = \"Delay\";\r\n    LGAudioDelay.desc = \"Audio delay\";\r\n    LiteGraph.registerNodeType(\"audio/delay\", LGAudioDelay);\r\n\r\n    function LGAudioBiquadFilter() {\r\n        //default\r\n        this.properties = {\r\n            frequency: 350,\r\n            detune: 0,\r\n            Q: 1\r\n        };\r\n        this.addProperty(\"type\", \"lowpass\", \"enum\", {\r\n            values: [\r\n                \"lowpass\",\r\n                \"highpass\",\r\n                \"bandpass\",\r\n                \"lowshelf\",\r\n                \"highshelf\",\r\n                \"peaking\",\r\n                \"notch\",\r\n                \"allpass\"\r\n            ]\r\n        });\r\n\r\n        //create node\r\n        this.audionode = LGAudio.getAudioContext().createBiquadFilter();\r\n\r\n        //slots\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioBiquadFilter.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n            if (input.link == null) {\r\n                continue;\r\n            }\r\n            var v = this.getInputData(i);\r\n            if (v !== undefined) {\r\n                this.audionode[input.name].value = v;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioBiquadFilter.prototype.onGetInputs = function() {\r\n        return [[\"frequency\", \"number\"], [\"detune\", \"number\"], [\"Q\", \"number\"]];\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioBiquadFilter);\r\n\r\n    LGAudioBiquadFilter.title = \"BiquadFilter\";\r\n    LGAudioBiquadFilter.desc = \"Audio filter\";\r\n    LiteGraph.registerNodeType(\"audio/biquadfilter\", LGAudioBiquadFilter);\r\n\r\n    function LGAudioOscillatorNode() {\r\n        //default\r\n        this.properties = {\r\n            frequency: 440,\r\n            detune: 0,\r\n            type: \"sine\"\r\n        };\r\n        this.addProperty(\"type\", \"sine\", \"enum\", {\r\n            values: [\"sine\", \"square\", \"sawtooth\", \"triangle\", \"custom\"]\r\n        });\r\n\r\n        //create node\r\n        this.audionode = LGAudio.getAudioContext().createOscillator();\r\n\r\n        //slots\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioOscillatorNode.prototype.onStart = function() {\r\n        if (!this.audionode.started) {\r\n            this.audionode.started = true;\r\n            try {\r\n                this.audionode.start();\r\n            } catch (err) {}\r\n        }\r\n    };\r\n\r\n    LGAudioOscillatorNode.prototype.onStop = function() {\r\n        if (this.audionode.started) {\r\n            this.audionode.started = false;\r\n            this.audionode.stop();\r\n        }\r\n    };\r\n\r\n    LGAudioOscillatorNode.prototype.onPause = function() {\r\n        this.onStop();\r\n    };\r\n\r\n    LGAudioOscillatorNode.prototype.onUnpause = function() {\r\n        this.onStart();\r\n    };\r\n\r\n    LGAudioOscillatorNode.prototype.onExecute = function() {\r\n        if (!this.inputs || !this.inputs.length) {\r\n            return;\r\n        }\r\n\r\n        for (var i = 0; i < this.inputs.length; ++i) {\r\n            var input = this.inputs[i];\r\n            if (input.link == null) {\r\n                continue;\r\n            }\r\n            var v = this.getInputData(i);\r\n            if (v !== undefined) {\r\n                this.audionode[input.name].value = v;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioOscillatorNode.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"frequency\", \"number\"],\r\n            [\"detune\", \"number\"],\r\n            [\"type\", \"string\"]\r\n        ];\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioOscillatorNode);\r\n\r\n    LGAudioOscillatorNode.title = \"Oscillator\";\r\n    LGAudioOscillatorNode.desc = \"Oscillator\";\r\n    LiteGraph.registerNodeType(\"audio/oscillator\", LGAudioOscillatorNode);\r\n\r\n    //*****************************************************\r\n\r\n    //EXTRA\r\n\r\n    function LGAudioVisualization() {\r\n        this.properties = {\r\n            continuous: true,\r\n            mark: -1\r\n        };\r\n\r\n        this.addInput(\"data\", \"array\");\r\n        this.addInput(\"mark\", \"number\");\r\n        this.size = [300, 200];\r\n        this._last_buffer = null;\r\n    }\r\n\r\n    LGAudioVisualization.prototype.onExecute = function() {\r\n        this._last_buffer = this.getInputData(0);\r\n        var v = this.getInputData(1);\r\n        if (v !== undefined) {\r\n            this.properties.mark = v;\r\n        }\r\n        this.setDirtyCanvas(true, false);\r\n    };\r\n\r\n    LGAudioVisualization.prototype.onDrawForeground = function(ctx) {\r\n        if (!this._last_buffer) {\r\n            return;\r\n        }\r\n\r\n        var buffer = this._last_buffer;\r\n\r\n        //delta represents how many samples we advance per pixel\r\n        var delta = buffer.length / this.size[0];\r\n        var h = this.size[1];\r\n\r\n        ctx.fillStyle = \"black\";\r\n        ctx.fillRect(0, 0, this.size[0], this.size[1]);\r\n        ctx.strokeStyle = \"white\";\r\n        ctx.beginPath();\r\n        var x = 0;\r\n\r\n        if (this.properties.continuous) {\r\n            ctx.moveTo(x, h);\r\n            for (var i = 0; i < buffer.length; i += delta) {\r\n                ctx.lineTo(x, h - (buffer[i | 0] / 255) * h);\r\n                x++;\r\n            }\r\n        } else {\r\n            for (var i = 0; i < buffer.length; i += delta) {\r\n                ctx.moveTo(x + 0.5, h);\r\n                ctx.lineTo(x + 0.5, h - (buffer[i | 0] / 255) * h);\r\n                x++;\r\n            }\r\n        }\r\n        ctx.stroke();\r\n\r\n        if (this.properties.mark >= 0) {\r\n            var samplerate = LGAudio.getAudioContext().sampleRate;\r\n            var binfreq = samplerate / buffer.length;\r\n            var x = (2 * (this.properties.mark / binfreq)) / delta;\r\n            if (x >= this.size[0]) {\r\n                x = this.size[0] - 1;\r\n            }\r\n            ctx.strokeStyle = \"red\";\r\n            ctx.beginPath();\r\n            ctx.moveTo(x, h);\r\n            ctx.lineTo(x, 0);\r\n            ctx.stroke();\r\n        }\r\n    };\r\n\r\n    LGAudioVisualization.title = \"Visualization\";\r\n    LGAudioVisualization.desc = \"Audio Visualization\";\r\n    LiteGraph.registerNodeType(\"audio/visualization\", LGAudioVisualization);\r\n\r\n    function LGAudioBandSignal() {\r\n        //default\r\n        this.properties = {\r\n            band: 440,\r\n            amplitude: 1\r\n        };\r\n\r\n        this.addInput(\"freqs\", \"array\");\r\n        this.addOutput(\"signal\", \"number\");\r\n    }\r\n\r\n    LGAudioBandSignal.prototype.onExecute = function() {\r\n        this._freqs = this.getInputData(0);\r\n        if (!this._freqs) {\r\n            return;\r\n        }\r\n\r\n        var band = this.properties.band;\r\n        var v = this.getInputData(1);\r\n        if (v !== undefined) {\r\n            band = v;\r\n        }\r\n\r\n        var samplerate = LGAudio.getAudioContext().sampleRate;\r\n        var binfreq = samplerate / this._freqs.length;\r\n        var index = 2 * (band / binfreq);\r\n        var v = 0;\r\n        if (index < 0) {\r\n            v = this._freqs[0];\r\n        }\r\n        if (index >= this._freqs.length) {\r\n            v = this._freqs[this._freqs.length - 1];\r\n        } else {\r\n            var pos = index | 0;\r\n            var v0 = this._freqs[pos];\r\n            var v1 = this._freqs[pos + 1];\r\n            var f = index - pos;\r\n            v = v0 * (1 - f) + v1 * f;\r\n        }\r\n\r\n        this.setOutputData(0, (v / 255) * this.properties.amplitude);\r\n    };\r\n\r\n    LGAudioBandSignal.prototype.onGetInputs = function() {\r\n        return [[\"band\", \"number\"]];\r\n    };\r\n\r\n    LGAudioBandSignal.title = \"Signal\";\r\n    LGAudioBandSignal.desc = \"extract the signal of some frequency\";\r\n    LiteGraph.registerNodeType(\"audio/signal\", LGAudioBandSignal);\r\n\r\n    function LGAudioScript() {\r\n        if (!LGAudioScript.default_code) {\r\n            var code = LGAudioScript.default_function.toString();\r\n            var index = code.indexOf(\"{\") + 1;\r\n            var index2 = code.lastIndexOf(\"}\");\r\n            LGAudioScript.default_code = code.substr(index, index2 - index);\r\n        }\r\n\r\n        //default\r\n        this.properties = {\r\n            code: LGAudioScript.default_code\r\n        };\r\n\r\n        //create node\r\n        var ctx = LGAudio.getAudioContext();\r\n        if (ctx.createScriptProcessor) {\r\n            this.audionode = ctx.createScriptProcessor(4096, 1, 1);\r\n        }\r\n        //buffer size, input channels, output channels\r\n        else {\r\n            console.warn(\"ScriptProcessorNode deprecated\");\r\n            this.audionode = ctx.createGain(); //bypass audio\r\n        }\r\n\r\n        this.processCode();\r\n        if (!LGAudioScript._bypass_function) {\r\n            LGAudioScript._bypass_function = this.audionode.onaudioprocess;\r\n        }\r\n\r\n        //slots\r\n        this.addInput(\"in\", \"audio\");\r\n        this.addOutput(\"out\", \"audio\");\r\n    }\r\n\r\n    LGAudioScript.prototype.onAdded = function(graph) {\r\n        if (graph.status == LGraph.STATUS_RUNNING) {\r\n            this.audionode.onaudioprocess = this._callback;\r\n        }\r\n    };\r\n\r\n    LGAudioScript[\"@code\"] = { widget: \"code\", type: \"code\" };\r\n\r\n    LGAudioScript.prototype.onStart = function() {\r\n        this.audionode.onaudioprocess = this._callback;\r\n    };\r\n\r\n    LGAudioScript.prototype.onStop = function() {\r\n        this.audionode.onaudioprocess = LGAudioScript._bypass_function;\r\n    };\r\n\r\n    LGAudioScript.prototype.onPause = function() {\r\n        this.audionode.onaudioprocess = LGAudioScript._bypass_function;\r\n    };\r\n\r\n    LGAudioScript.prototype.onUnpause = function() {\r\n        this.audionode.onaudioprocess = this._callback;\r\n    };\r\n\r\n    LGAudioScript.prototype.onExecute = function() {\r\n        //nothing! because we need an onExecute to receive onStart... fix that\r\n    };\r\n\r\n    LGAudioScript.prototype.onRemoved = function() {\r\n        this.audionode.onaudioprocess = LGAudioScript._bypass_function;\r\n    };\r\n\r\n    LGAudioScript.prototype.processCode = function() {\r\n        try {\r\n            var func = new Function(\"properties\", this.properties.code);\r\n            this._script = new func(this.properties);\r\n            this._old_code = this.properties.code;\r\n            this._callback = this._script.onaudioprocess;\r\n        } catch (err) {\r\n            console.error(\"Error in onaudioprocess code\", err);\r\n            this._callback = LGAudioScript._bypass_function;\r\n            this.audionode.onaudioprocess = this._callback;\r\n        }\r\n    };\r\n\r\n    LGAudioScript.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"code\") {\r\n            this.properties.code = value;\r\n            this.processCode();\r\n            if (this.graph && this.graph.status == LGraph.STATUS_RUNNING) {\r\n                this.audionode.onaudioprocess = this._callback;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGAudioScript.default_function = function() {\r\n        this.onaudioprocess = function(audioProcessingEvent) {\r\n            // The input buffer is the song we loaded earlier\r\n            var inputBuffer = audioProcessingEvent.inputBuffer;\r\n\r\n            // The output buffer contains the samples that will be modified and played\r\n            var outputBuffer = audioProcessingEvent.outputBuffer;\r\n\r\n            // Loop through the output channels (in this case there is only one)\r\n            for (\r\n                var channel = 0;\r\n                channel < outputBuffer.numberOfChannels;\r\n                channel++\r\n            ) {\r\n                var inputData = inputBuffer.getChannelData(channel);\r\n                var outputData = outputBuffer.getChannelData(channel);\r\n\r\n                // Loop through the 4096 samples\r\n                for (var sample = 0; sample < inputBuffer.length; sample++) {\r\n                    // make output equal to the same as the input\r\n                    outputData[sample] = inputData[sample];\r\n                }\r\n            }\r\n        };\r\n    };\r\n\r\n    LGAudio.createAudioNodeWrapper(LGAudioScript);\r\n\r\n    LGAudioScript.title = \"Script\";\r\n    LGAudioScript.desc = \"apply script to signal\";\r\n    LiteGraph.registerNodeType(\"audio/script\", LGAudioScript);\r\n\r\n    function LGAudioDestination() {\r\n        this.audionode = LGAudio.getAudioContext().destination;\r\n        this.addInput(\"in\", \"audio\");\r\n    }\r\n\r\n    LGAudioDestination.title = \"Destination\";\r\n    LGAudioDestination.desc = \"Audio output\";\r\n    LiteGraph.registerNodeType(\"audio/destination\", LGAudioDestination);\r\n})(this);\r\n"
  },
  {
    "path": "src/nodes/base.js",
    "content": "//basic nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    //Constant\r\n    function Time() {\r\n        this.addOutput(\"in ms\", \"number\");\r\n        this.addOutput(\"in sec\", \"number\");\r\n    }\r\n\r\n    Time.title = \"Time\";\r\n    Time.desc = \"Time\";\r\n\r\n    Time.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.graph.globaltime * 1000);\r\n        this.setOutputData(1, this.graph.globaltime);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/time\", Time);\r\n\r\n    //Subgraph: a node that contains a graph\r\n    function Subgraph() {\r\n        var that = this;\r\n        this.size = [140, 80];\r\n        this.properties = { enabled: true };\r\n        this.enabled = true;\r\n\r\n        //create inner graph\r\n        this.subgraph = new LiteGraph.LGraph();\r\n        this.subgraph._subgraph_node = this;\r\n        this.subgraph._is_subgraph = true;\r\n\r\n        this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this);\r\n\r\n\t\t//nodes input node added inside\r\n        this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this);\r\n        this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this);\r\n        this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(this);\r\n        this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this);\r\n\r\n        this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this);\r\n        this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this);\r\n        this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(this);\r\n        this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this);\r\n    }\r\n\r\n    Subgraph.title = \"Subgraph\";\r\n    Subgraph.desc = \"Graph inside a node\";\r\n    Subgraph.title_color = \"#334\";\r\n\r\n    Subgraph.prototype.onGetInputs = function() {\r\n        return [[\"enabled\", \"boolean\"]];\r\n    };\r\n\r\n\t/*\r\n    Subgraph.prototype.onDrawTitle = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        ctx.fillStyle = \"#555\";\r\n        var w = LiteGraph.NODE_TITLE_HEIGHT;\r\n        var x = this.size[0] - w;\r\n        ctx.fillRect(x, -w, w, w);\r\n        ctx.fillStyle = \"#333\";\r\n        ctx.beginPath();\r\n        ctx.moveTo(x + w * 0.2, -w * 0.6);\r\n        ctx.lineTo(x + w * 0.8, -w * 0.6);\r\n        ctx.lineTo(x + w * 0.5, -w * 0.3);\r\n        ctx.fill();\r\n    };\r\n\t*/\r\n\r\n    Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) {\r\n        var that = this;\r\n        setTimeout(function() {\r\n            graphcanvas.openSubgraph(that.subgraph);\r\n        }, 10);\r\n    };\r\n\r\n\t/*\r\n    Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) {\r\n        if (\r\n            !this.flags.collapsed &&\r\n            pos[0] > this.size[0] - LiteGraph.NODE_TITLE_HEIGHT &&\r\n            pos[1] < 0\r\n        ) {\r\n            var that = this;\r\n            setTimeout(function() {\r\n                graphcanvas.openSubgraph(that.subgraph);\r\n            }, 10);\r\n        }\r\n    };\r\n\t*/\r\n\r\n    Subgraph.prototype.onAction = function(action, param) {\r\n        this.subgraph.onAction(action, param);\r\n    };\r\n\r\n    Subgraph.prototype.onExecute = function() {\r\n        this.enabled = this.getInputOrProperty(\"enabled\");\r\n        if (!this.enabled) {\r\n            return;\r\n        }\r\n\r\n        //send inputs to subgraph global inputs\r\n        if (this.inputs) {\r\n            for (var i = 0; i < this.inputs.length; i++) {\r\n                var input = this.inputs[i];\r\n                var value = this.getInputData(i);\r\n                this.subgraph.setInputData(input.name, value);\r\n            }\r\n        }\r\n\r\n        //execute\r\n        this.subgraph.runStep();\r\n\r\n        //send subgraph global outputs to outputs\r\n        if (this.outputs) {\r\n            for (var i = 0; i < this.outputs.length; i++) {\r\n                var output = this.outputs[i];\r\n                var value = this.subgraph.getOutputData(output.name);\r\n                this.setOutputData(i, value);\r\n            }\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.sendEventToAllNodes = function(eventname, param, mode) {\r\n        if (this.enabled) {\r\n            this.subgraph.sendEventToAllNodes(eventname, param, mode);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onDrawBackground = function (ctx, graphcanvas, canvas, pos) {\r\n        if (this.flags.collapsed)\r\n            return;\r\n        var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n        // button\r\n        var over = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0], LiteGraph.NODE_TITLE_HEIGHT);\r\n        let overleft = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0] / 2, LiteGraph.NODE_TITLE_HEIGHT)\r\n        ctx.fillStyle = over ? \"#555\" : \"#222\";\r\n        ctx.beginPath();\r\n        if (this._shape == LiteGraph.BOX_SHAPE) {\r\n            if (overleft) {\r\n                ctx.rect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT);\r\n            } else {\r\n                ctx.rect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT);\r\n            }\r\n        }\r\n        else {\r\n            if (overleft) {\r\n                ctx.roundRect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]);\r\n            } else {\r\n                ctx.roundRect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]);\r\n            }\r\n        }\r\n        if (over) {\r\n            ctx.fill();\r\n        } else {\r\n            ctx.fillRect(0, y, this.size[0] + 1, LiteGraph.NODE_TITLE_HEIGHT);\r\n        }\r\n        // button\r\n        ctx.textAlign = \"center\";\r\n        ctx.font = \"24px Arial\";\r\n        ctx.fillStyle = over ? \"#DDD\" : \"#999\";\r\n        ctx.fillText(\"+\", this.size[0] * 0.25, y + 24);\r\n        ctx.fillText(\"+\", this.size[0] * 0.75, y + 24);\r\n    }\r\n\r\n    // Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas)\r\n    // {\r\n    // \tvar y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n    // \tif(localpos[1] > y)\r\n    // \t{\r\n    // \t\tgraphcanvas.showSubgraphPropertiesDialog(this);\r\n    // \t}\r\n    // }\r\n    Subgraph.prototype.onMouseDown = function (e, localpos, graphcanvas) {\r\n        var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n        console.log(0)\r\n        if (localpos[1] > y) {\r\n            if (localpos[0] < this.size[0] / 2) {\r\n                console.log(1)\r\n                graphcanvas.showSubgraphPropertiesDialog(this);\r\n            } else {\r\n                console.log(2)\r\n                graphcanvas.showSubgraphPropertiesDialogRight(this);\r\n            }\r\n        }\r\n    }\r\n\tSubgraph.prototype.computeSize = function()\r\n\t{\r\n\t\tvar num_inputs = this.inputs ? this.inputs.length : 0;\r\n\t\tvar num_outputs = this.outputs ? this.outputs.length : 0;\r\n\t\treturn [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ];\r\n\t}\r\n\r\n    //**** INPUTS ***********************************\r\n    Subgraph.prototype.onSubgraphTrigger = function(event, param) {\r\n        var slot = this.findOutputSlot(event);\r\n        if (slot != -1) {\r\n            this.triggerSlot(slot);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphNewInput = function(name, type) {\r\n        var slot = this.findInputSlot(name);\r\n        if (slot == -1) {\r\n            //add input to the node\r\n            this.addInput(name, type);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRenamedInput = function(oldname, name) {\r\n        var slot = this.findInputSlot(oldname);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getInputInfo(slot);\r\n        info.name = name;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphTypeChangeInput = function(name, type) {\r\n        var slot = this.findInputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getInputInfo(slot);\r\n        info.type = type;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRemovedInput = function(name) {\r\n        var slot = this.findInputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        this.removeInput(slot);\r\n    };\r\n\r\n    //**** OUTPUTS ***********************************\r\n    Subgraph.prototype.onSubgraphNewOutput = function(name, type) {\r\n        var slot = this.findOutputSlot(name);\r\n        if (slot == -1) {\r\n            this.addOutput(name, type);\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRenamedOutput = function(oldname, name) {\r\n        var slot = this.findOutputSlot(oldname);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getOutputInfo(slot);\r\n        info.name = name;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphTypeChangeOutput = function(name, type) {\r\n        var slot = this.findOutputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        var info = this.getOutputInfo(slot);\r\n        info.type = type;\r\n    };\r\n\r\n    Subgraph.prototype.onSubgraphRemovedOutput = function(name) {\r\n        var slot = this.findOutputSlot(name);\r\n        if (slot == -1) {\r\n            return;\r\n        }\r\n        this.removeOutput(slot);\r\n    };\r\n    // *****************************************************\r\n\r\n    Subgraph.prototype.getExtraMenuOptions = function(graphcanvas) {\r\n        var that = this;\r\n        return [\r\n            {\r\n                content: \"Open\",\r\n                callback: function() {\r\n                    graphcanvas.openSubgraph(that.subgraph);\r\n                }\r\n            }\r\n        ];\r\n    };\r\n\r\n    Subgraph.prototype.onResize = function(size) {\r\n        size[1] += 20;\r\n    };\r\n\r\n    Subgraph.prototype.serialize = function() {\r\n        var data = LiteGraph.LGraphNode.prototype.serialize.call(this);\r\n        data.subgraph = this.subgraph.serialize();\r\n        return data;\r\n    };\r\n    //no need to define node.configure, the default method detects node.subgraph and passes the object to node.subgraph.configure()\r\n\r\n    Subgraph.prototype.reassignSubgraphUUIDs = function(graph) {\r\n        const idMap = { nodeIDs: {}, linkIDs: {} }\r\n\r\n        for (const node of graph.nodes) {\r\n            const oldID = node.id\r\n            const newID = LiteGraph.uuidv4()\r\n            node.id = newID\r\n\r\n            if (idMap.nodeIDs[oldID] || idMap.nodeIDs[newID]) {\r\n                throw new Error(`New/old node UUID wasn't unique in changed map! ${oldID} ${newID}`)\r\n            }\r\n\r\n            idMap.nodeIDs[oldID] = newID\r\n            idMap.nodeIDs[newID] = oldID\r\n        }\r\n\r\n        for (const link of graph.links) {\r\n            const oldID = link[0]\r\n            const newID = LiteGraph.uuidv4();\r\n            link[0] = newID\r\n\r\n            if (idMap.linkIDs[oldID] || idMap.linkIDs[newID]) {\r\n                throw new Error(`New/old link UUID wasn't unique in changed map! ${oldID} ${newID}`)\r\n            }\r\n\r\n            idMap.linkIDs[oldID] = newID\r\n            idMap.linkIDs[newID] = oldID\r\n\r\n            const nodeFrom = link[1]\r\n            const nodeTo = link[3]\r\n\r\n            if (!idMap.nodeIDs[nodeFrom]) {\r\n                throw new Error(`Old node UUID not found in mapping! ${nodeFrom}`)\r\n            }\r\n\r\n            link[1] = idMap.nodeIDs[nodeFrom]\r\n\r\n            if (!idMap.nodeIDs[nodeTo]) {\r\n                throw new Error(`Old node UUID not found in mapping! ${nodeTo}`)\r\n            }\r\n\r\n            link[3] = idMap.nodeIDs[nodeTo]\r\n        }\r\n\r\n        // Reconnect links\r\n        for (const node of graph.nodes) {\r\n            if (node.inputs) {\r\n                for (const input of node.inputs) {\r\n                    if (input.link) {\r\n                        input.link = idMap.linkIDs[input.link]\r\n                    }\r\n                }\r\n            }\r\n            if (node.outputs) {\r\n                for (const output of node.outputs) {\r\n                    if (output.links) {\r\n                        output.links = output.links.map(l => idMap.linkIDs[l]);\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        // Recurse!\r\n        for (const node of graph.nodes) {\r\n            if (node.type === \"graph/subgraph\") {\r\n                const merge = reassignGraphUUIDs(node.subgraph);\r\n                idMap.nodeIDs.assign(merge.nodeIDs)\r\n                idMap.linkIDs.assign(merge.linkIDs)\r\n            }\r\n        }\r\n    };\r\n\r\n    Subgraph.prototype.clone = function() {\r\n        var node = LiteGraph.createNode(this.type);\r\n        var data = this.serialize();\r\n\r\n        if (LiteGraph.use_uuids) {\r\n            // LGraph.serialize() seems to reuse objects in the original graph. But we\r\n            // need to change node IDs here, so clone it first.\r\n            const subgraph = LiteGraph.cloneObject(data.subgraph)\r\n\r\n            this.reassignSubgraphUUIDs(subgraph);\r\n\r\n            data.subgraph = subgraph;\r\n        }\r\n\r\n        delete data[\"id\"];\r\n        delete data[\"inputs\"];\r\n        delete data[\"outputs\"];\r\n        node.configure(data);\r\n        return node;\r\n    };\r\n\r\n\tSubgraph.prototype.buildFromNodes = function(nodes)\r\n\t{\r\n\t\t//clear all?\r\n\t\t//TODO\r\n\r\n\t\t//nodes that connect data between parent graph and subgraph\r\n\t\tvar subgraph_inputs = [];\r\n\t\tvar subgraph_outputs = [];\r\n\r\n\t\t//mark inner nodes\r\n\t\tvar ids = {};\r\n\t\tvar min_x = 0;\r\n\t\tvar max_x = 0;\r\n\t\tfor(var i = 0; i < nodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar node = nodes[i];\r\n\t\t\tids[ node.id ] = node;\r\n\t\t\tmin_x = Math.min( node.pos[0], min_x );\r\n\t\t\tmax_x = Math.max( node.pos[0], min_x );\r\n\t\t}\r\n\t\t\r\n\t\tvar last_input_y = 0;\r\n\t\tvar last_output_y = 0;\r\n\r\n\t\tfor(var i = 0; i < nodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar node = nodes[i];\r\n\t\t\t//check inputs\r\n\t\t\tif( node.inputs )\r\n\t\t\t\tfor(var j = 0; j < node.inputs.length; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar input = node.inputs[j];\r\n\t\t\t\t\tif( !input || !input.link )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tvar link = node.graph.links[ input.link ];\r\n\t\t\t\t\tif(!link)\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tif( ids[ link.origin_id ] )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t//this.addInput(input.name,link.type);\r\n\t\t\t\t\tthis.subgraph.addInput(input.name,link.type);\r\n\t\t\t\t\t/*\r\n\t\t\t\t\tvar input_node = LiteGraph.createNode(\"graph/input\");\r\n\t\t\t\t\tthis.subgraph.add( input_node );\r\n\t\t\t\t\tinput_node.pos = [min_x - 200, last_input_y ];\r\n\t\t\t\t\tlast_input_y += 100;\r\n\t\t\t\t\t*/\r\n\t\t\t\t}\r\n\r\n\t\t\t//check outputs\r\n\t\t\tif( node.outputs )\r\n\t\t\t\tfor(var j = 0; j < node.outputs.length; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar output = node.outputs[j];\r\n\t\t\t\t\tif( !output || !output.links || !output.links.length )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tvar is_external = false;\r\n\t\t\t\t\tfor(var k = 0; k < output.links.length; ++k)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar link = node.graph.links[ output.links[k] ];\r\n\t\t\t\t\t\tif(!link)\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\tif( ids[ link.target_id ] )\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\tis_external = true;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif(!is_external)\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t//this.addOutput(output.name,output.type);\r\n\t\t\t\t\t/*\r\n\t\t\t\t\tvar output_node = LiteGraph.createNode(\"graph/output\");\r\n\t\t\t\t\tthis.subgraph.add( output_node );\r\n\t\t\t\t\toutput_node.pos = [max_x + 50, last_output_y ];\r\n\t\t\t\t\tlast_output_y += 100;\r\n\t\t\t\t\t*/\r\n\t\t\t\t}\r\n\t\t}\r\n\r\n\t\t//detect inputs and outputs\r\n\t\t\t//split every connection in two data_connection nodes\r\n\t\t\t//keep track of internal connections\r\n\t\t\t//connect external connections\r\n\r\n\t\t//clone nodes inside subgraph and try to reconnect them\r\n\r\n\t\t//connect edge subgraph nodes to extarnal connections nodes\r\n\t}\r\n\r\n    LiteGraph.Subgraph = Subgraph;\r\n    LiteGraph.registerNodeType(\"graph/subgraph\", Subgraph);\r\n\r\n    //Input for a subgraph\r\n    function GraphInput() {\r\n        this.addOutput(\"\", \"number\");\r\n\r\n        this.name_in_graph = \"\";\r\n        this.properties = {\r\n\t\t\tname: \"\",\r\n\t\t\ttype: \"number\",\r\n\t\t\tvalue: 0\r\n\t\t}; \r\n\r\n        var that = this;\r\n\r\n        this.name_widget = this.addWidget(\r\n            \"text\",\r\n            \"Name\",\r\n            this.properties.name,\r\n            function(v) {\r\n                if (!v) {\r\n                    return;\r\n                }\r\n                that.setProperty(\"name\",v);\r\n            }\r\n        );\r\n        this.type_widget = this.addWidget(\r\n            \"text\",\r\n            \"Type\",\r\n            this.properties.type,\r\n            function(v) {\r\n\t\t\t\tthat.setProperty(\"type\",v);\r\n            }\r\n        );\r\n\r\n        this.value_widget = this.addWidget(\r\n            \"number\",\r\n            \"Value\",\r\n            this.properties.value,\r\n            function(v) {\r\n                that.setProperty(\"value\",v);\r\n            }\r\n        );\r\n\r\n        this.widgets_up = true;\r\n        this.size = [180, 90];\r\n    }\r\n\r\n    GraphInput.title = \"Input\";\r\n    GraphInput.desc = \"Input of the graph\";\r\n\r\n\tGraphInput.prototype.onConfigure = function()\r\n\r\n\t{\r\n\t\tthis.updateType();\r\n\t}\r\n\r\n\t//ensures the type in the node output and the type in the associated graph input are the same\r\n\tGraphInput.prototype.updateType = function()\r\n\t{\r\n\t\tvar type = this.properties.type;\r\n\t\tthis.type_widget.value = type;\r\n\r\n\t\t//update output\r\n\t\tif(this.outputs[0].type != type)\r\n\t\t{\r\n\t        if (!LiteGraph.isValidConnection(this.outputs[0].type,type))\r\n\t\t\t\tthis.disconnectOutput(0);\r\n\t\t\tthis.outputs[0].type = type;\r\n\t\t}\r\n\r\n\t\t//update widget\r\n\t\tif(type == \"number\")\r\n\t\t{\r\n\t\t\tthis.value_widget.type = \"number\";\r\n\t\t\tthis.value_widget.value = 0;\r\n\t\t}\r\n\t\telse if(type == \"boolean\")\r\n\t\t{\r\n\t\t\tthis.value_widget.type = \"toggle\";\r\n\t\t\tthis.value_widget.value = true;\r\n\t\t}\r\n\t\telse if(type == \"string\")\r\n\t\t{\r\n\t\t\tthis.value_widget.type = \"text\";\r\n\t\t\tthis.value_widget.value = \"\";\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.value_widget.type = null;\r\n\t\t\tthis.value_widget.value = null;\r\n\t\t}\r\n\t\tthis.properties.value = this.value_widget.value;\r\n\r\n\t\t//update graph\r\n\t\tif (this.graph && this.name_in_graph) {\r\n\t\t\tthis.graph.changeInputType(this.name_in_graph, type);\r\n\t\t}\r\n\t}\r\n\r\n\t//this is executed AFTER the property has changed\r\n\tGraphInput.prototype.onPropertyChanged = function(name,v)\r\n\t{\r\n\t\tif( name == \"name\" )\r\n\t\t{\r\n\t\t\tif (v == \"\" || v == this.name_in_graph || v == \"enabled\") {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tif(this.graph)\r\n\t\t\t{\r\n\t\t\t\tif (this.name_in_graph) {\r\n\t\t\t\t\t//already added\r\n\t\t\t\t\tthis.graph.renameInput( this.name_in_graph, v );\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.graph.addInput( v, this.properties.type );\r\n\t\t\t\t}\r\n\t\t\t} //what if not?!\r\n\t\t\tthis.name_widget.value = v;\r\n\t\t\tthis.name_in_graph = v;\r\n\t\t}\r\n\t\telse if( name == \"type\" )\r\n\t\t{\r\n\t\t\tthis.updateType();\r\n\t\t}\r\n\t\telse if( name == \"value\" )\r\n\t\t{\r\n\t\t}\r\n\t}\r\n\r\n    GraphInput.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.name;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    GraphInput.prototype.onAction = function(action, param) {\r\n        if (this.properties.type == LiteGraph.EVENT) {\r\n            this.triggerSlot(0, param);\r\n        }\r\n    };\r\n\r\n    GraphInput.prototype.onExecute = function() {\r\n        var name = this.properties.name;\r\n        //read from global input\r\n        var data = this.graph.inputs[name];\r\n        if (!data) {\r\n            this.setOutputData(0, this.properties.value );\r\n\t\t\treturn;\r\n        }\r\n\r\n        this.setOutputData(0, data.value !== undefined ? data.value : this.properties.value );\r\n    };\r\n\r\n    GraphInput.prototype.onRemoved = function() {\r\n        if (this.name_in_graph) {\r\n            this.graph.removeInput(this.name_in_graph);\r\n        }\r\n    };\r\n\r\n    LiteGraph.GraphInput = GraphInput;\r\n    LiteGraph.registerNodeType(\"graph/input\", GraphInput);\r\n\r\n    //Output for a subgraph\r\n    function GraphOutput() {\r\n        this.addInput(\"\", \"\");\r\n\r\n        this.name_in_graph = \"\";\r\n        this.properties = { name: \"\", type: \"\" };\r\n        var that = this;\r\n\r\n        // Object.defineProperty(this.properties, \"name\", {\r\n        //     get: function() {\r\n        //         return that.name_in_graph;\r\n        //     },\r\n        //     set: function(v) {\r\n        //         if (v == \"\" || v == that.name_in_graph) {\r\n        //             return;\r\n        //         }\r\n        //         if (that.name_in_graph) {\r\n        //             //already added\r\n        //             that.graph.renameOutput(that.name_in_graph, v);\r\n        //         } else {\r\n        //             that.graph.addOutput(v, that.properties.type);\r\n        //         }\r\n        //         that.name_widget.value = v;\r\n        //         that.name_in_graph = v;\r\n        //     },\r\n        //     enumerable: true\r\n        // });\r\n\r\n        // Object.defineProperty(this.properties, \"type\", {\r\n        //     get: function() {\r\n        //         return that.inputs[0].type;\r\n        //     },\r\n        //     set: function(v) {\r\n        //         if (v == \"action\" || v == \"event\") {\r\n        //             v = LiteGraph.ACTION;\r\n        //         }\r\n\t\t//         if (!LiteGraph.isValidConnection(that.inputs[0].type,v))\r\n\t\t// \t\t\tthat.disconnectInput(0);\r\n        //         that.inputs[0].type = v;\r\n        //         if (that.name_in_graph) {\r\n        //             //already added\r\n        //             that.graph.changeOutputType(\r\n        //                 that.name_in_graph,\r\n        //                 that.inputs[0].type\r\n        //             );\r\n        //         }\r\n        //         that.type_widget.value = v || \"\";\r\n        //     },\r\n        //     enumerable: true\r\n        // });\r\n\r\n        this.name_widget = this.addWidget(\"text\",\"Name\",this.properties.name,\"name\");\r\n        this.type_widget = this.addWidget(\"text\",\"Type\",this.properties.type,\"type\");\r\n        this.widgets_up = true;\r\n        this.size = [180, 60];\r\n    }\r\n\r\n    GraphOutput.title = \"Output\";\r\n    GraphOutput.desc = \"Output of the graph\";\r\n\r\n    GraphOutput.prototype.onPropertyChanged = function (name, v) {\r\n        if (name == \"name\") {\r\n            if (v == \"\" || v == this.name_in_graph || v == \"enabled\") {\r\n                return false;\r\n            }\r\n            if (this.graph) {\r\n                if (this.name_in_graph) {\r\n                    //already added\r\n                    this.graph.renameOutput(this.name_in_graph, v);\r\n                } else {\r\n                    this.graph.addOutput(v, this.properties.type);\r\n                }\r\n            } //what if not?!\r\n            this.name_widget.value = v;\r\n            this.name_in_graph = v;\r\n        }\r\n        else if (name == \"type\") {\r\n            this.updateType();\r\n        }\r\n        else if (name == \"value\") {\r\n        }\r\n    }\r\n     \r\n    GraphOutput.prototype.updateType = function () {\r\n        var type = this.properties.type;\r\n        if (this.type_widget)\r\n            this.type_widget.value = type;\r\n\r\n        //update output\r\n        if (this.inputs[0].type != type) {\r\n\r\n\t\t\tif ( type == \"action\" || type == \"event\")\r\n\t            type = LiteGraph.EVENT;\r\n\t\t\tif (!LiteGraph.isValidConnection(this.inputs[0].type, type))\r\n\t\t\t\tthis.disconnectInput(0);\r\n\t\t\tthis.inputs[0].type = type;\r\n        }\r\n\r\n        //update graph\r\n        if (this.graph && this.name_in_graph) {\r\n            this.graph.changeOutputType(this.name_in_graph, type);\r\n        }\r\n    }\r\n\r\n\r\n\r\n    GraphOutput.prototype.onExecute = function() {\r\n        this._value = this.getInputData(0);\r\n        this.graph.setOutputData(this.properties.name, this._value);\r\n    };\r\n\r\n    GraphOutput.prototype.onAction = function(action, param) {\r\n        if (this.properties.type == LiteGraph.ACTION) {\r\n            this.graph.trigger( this.properties.name, param );\r\n        }\r\n    };\r\n\r\n    GraphOutput.prototype.onRemoved = function() {\r\n        if (this.name_in_graph) {\r\n            this.graph.removeOutput(this.name_in_graph);\r\n        }\r\n    };\r\n\r\n    GraphOutput.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.name;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    LiteGraph.GraphOutput = GraphOutput;\r\n    LiteGraph.registerNodeType(\"graph/output\", GraphOutput);\r\n\r\n    //Constant\r\n    function ConstantNumber() {\r\n        this.addOutput(\"value\", \"number\");\r\n        this.addProperty(\"value\", 1.0);\r\n        this.widget = this.addWidget(\"number\",\"value\",1,\"value\");\r\n        this.widgets_up = true;\r\n        this.size = [180, 30];\r\n    }\r\n\r\n    ConstantNumber.title = \"Const Number\";\r\n    ConstantNumber.desc = \"Constant number\";\r\n\r\n    ConstantNumber.prototype.onExecute = function() {\r\n        this.setOutputData(0, parseFloat(this.properties[\"value\"]));\r\n    };\r\n\r\n    ConstantNumber.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.value;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n\tConstantNumber.prototype.setValue = function(v)\r\n\t{\r\n\t\tthis.setProperty(\"value\",v);\r\n\t}\r\n\r\n    ConstantNumber.prototype.onDrawBackground = function(ctx) {\r\n        //show the current value\r\n        this.outputs[0].label = this.properties[\"value\"].toFixed(3);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/const\", ConstantNumber);\r\n\r\n    function ConstantBoolean() {\r\n        this.addOutput(\"bool\", \"boolean\");\r\n        this.addProperty(\"value\", true);\r\n        this.widget = this.addWidget(\"toggle\",\"value\",true,\"value\");\r\n        this.serialize_widgets = true;\r\n        this.widgets_up = true;\r\n        this.size = [140, 30];\r\n    }\r\n\r\n    ConstantBoolean.title = \"Const Boolean\";\r\n    ConstantBoolean.desc = \"Constant boolean\";\r\n    ConstantBoolean.prototype.getTitle = ConstantNumber.prototype.getTitle;\r\n\r\n    ConstantBoolean.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.properties[\"value\"]);\r\n    };\r\n\r\n\tConstantBoolean.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n\tConstantBoolean.prototype.onGetInputs = function() {\r\n\t\treturn [[\"toggle\", LiteGraph.ACTION]];\r\n\t};\r\n\r\n\tConstantBoolean.prototype.onAction = function(action)\r\n\t{\r\n\t\tthis.setValue( !this.properties.value );\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/boolean\", ConstantBoolean);\r\n\r\n    function ConstantString() {\r\n        this.addOutput(\"string\", \"string\");\r\n        this.addProperty(\"value\", \"\");\r\n        this.widget = this.addWidget(\"text\",\"value\",\"\",\"value\");  //link to property value\r\n        this.widgets_up = true;\r\n        this.size = [180, 30];\r\n    }\r\n\r\n    ConstantString.title = \"Const String\";\r\n    ConstantString.desc = \"Constant string\";\r\n\r\n    ConstantString.prototype.getTitle = ConstantNumber.prototype.getTitle;\r\n\r\n    ConstantString.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.properties[\"value\"]);\r\n    };\r\n\r\n\tConstantString.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n\tConstantString.prototype.onDropFile = function(file)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tvar reader = new FileReader();\r\n\t\treader.onload = function(e)\r\n\t\t{\r\n\t\t\tthat.setProperty(\"value\",e.target.result);\r\n\t\t}\r\n\t\treader.readAsText(file);\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/string\", ConstantString);\r\n\r\n    function ConstantObject() {\r\n        this.addOutput(\"obj\", \"object\");\r\n        this.size = [120, 30];\r\n\t\tthis._object = {};\r\n    }\r\n\r\n    ConstantObject.title = \"Const Object\";\r\n    ConstantObject.desc = \"Constant Object\";\r\n\r\n    ConstantObject.prototype.onExecute = function() {\r\n        this.setOutputData(0, this._object);\r\n    };\r\n\r\n    LiteGraph.registerNodeType( \"basic/object\", ConstantObject );\r\n\r\n    function ConstantFile() {\r\n        this.addInput(\"url\", \"string\");\r\n        this.addOutput(\"file\", \"string\");\r\n        this.addProperty(\"url\", \"\");\r\n        this.addProperty(\"type\", \"text\");\r\n        this.widget = this.addWidget(\"text\",\"url\",\"\",\"url\");\r\n        this._data = null;\r\n    }\r\n\r\n    ConstantFile.title = \"Const File\";\r\n    ConstantFile.desc = \"Fetches a file from an url\";\r\n    ConstantFile[\"@type\"] = { type: \"enum\", values: [\"text\",\"arraybuffer\",\"blob\",\"json\"] };\r\n\r\n    ConstantFile.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"url\")\r\n\t\t{\r\n\t\t\tif( value == null || value == \"\")\r\n\t\t\t\tthis._data = null;\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.fetchFile(value);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n    ConstantFile.prototype.onExecute = function() {\r\n\t\tvar url = this.getInputData(0) || this.properties.url;\r\n\t\tif(url && (url != this._url || this._type != this.properties.type))\r\n\t\t\tthis.fetchFile(url);\r\n        this.setOutputData(0, this._data );\r\n    };\r\n\r\n\tConstantFile.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n    ConstantFile.prototype.fetchFile = function(url) {\r\n\t\tvar that = this;\r\n\t\tif(!url || url.constructor !== String)\r\n\t\t{\r\n\t\t\tthat._data = null;\r\n            that.boxcolor = null;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis._url = url;\r\n\t\tthis._type = this.properties.type;\r\n        if (url.substr(0, 4) == \"http\" && LiteGraph.proxy) {\r\n            url = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\r\n        }\r\n\t\tfetch(url)\r\n\t\t.then(function(response) {\r\n\t\t\tif(!response.ok)\r\n\t\t\t\t throw new Error(\"File not found\");\r\n\r\n\t\t\tif(that.properties.type == \"arraybuffer\")\r\n\t\t\t\treturn response.arrayBuffer();\r\n\t\t\telse if(that.properties.type == \"text\")\r\n\t\t\t\treturn response.text();\r\n\t\t\telse if(that.properties.type == \"json\")\r\n\t\t\t\treturn response.json();\r\n\t\t\telse if(that.properties.type == \"blob\")\r\n\t\t\t\treturn response.blob();\r\n\t\t})\r\n\t\t.then(function(data) {\r\n\t\t\tthat._data = data;\r\n            that.boxcolor = \"#AEA\";\r\n\t\t})\r\n\t\t.catch(function(error) {\r\n\t\t\tthat._data = null;\r\n            that.boxcolor = \"red\";\r\n\t\t\tconsole.error(\"error fetching file:\",url);\r\n\t\t});\r\n    };\r\n\r\n\tConstantFile.prototype.onDropFile = function(file)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tthis._url = file.name;\r\n\t\tthis._type = this.properties.type;\r\n\t\tthis.properties.url = file.name;\r\n\t\tvar reader = new FileReader();\r\n\t\treader.onload = function(e)\r\n\t\t{\r\n            that.boxcolor = \"#AEA\";\r\n\t\t\tvar v = e.target.result;\r\n\t\t\tif( that.properties.type == \"json\" )\r\n\t\t\t\tv = JSON.parse(v);\r\n\t\t\tthat._data = v;\r\n\t\t}\r\n\t\tif(that.properties.type == \"arraybuffer\")\r\n\t\t\treader.readAsArrayBuffer(file);\r\n\t\telse if(that.properties.type == \"text\" || that.properties.type == \"json\")\r\n\t\t\treader.readAsText(file);\r\n\t\telse if(that.properties.type == \"blob\")\r\n\t\t\treturn reader.readAsBinaryString(file);\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/file\", ConstantFile);\r\n\r\n\r\n//to store json objects\r\nfunction JSONParse() {\r\n\tthis.addInput(\"parse\", LiteGraph.ACTION);\r\n\tthis.addInput(\"json\", \"string\");\r\n\tthis.addOutput(\"done\", LiteGraph.EVENT);\r\n\tthis.addOutput(\"object\", \"object\");\r\n\tthis.widget = this.addWidget(\"button\",\"parse\",\"\",this.parse.bind(this));\r\n\tthis._str = null;\r\n\tthis._obj = null;\r\n}\r\n\r\nJSONParse.title = \"JSON Parse\";\r\nJSONParse.desc = \"Parses JSON String into object\";\r\n\r\nJSONParse.prototype.parse = function()\r\n{\r\n\tif(!this._str)\r\n\t\treturn;\r\n\r\n\ttry {\r\n\t\tthis._str = this.getInputData(1);\r\n\t\tthis._obj = JSON.parse(this._str);\r\n\t\tthis.boxcolor = \"#AEA\";\r\n\t\tthis.triggerSlot(0);\r\n\t} catch (err) {\r\n\t\tthis.boxcolor = \"red\";\r\n\t}\r\n}\r\n\r\nJSONParse.prototype.onExecute = function() {\r\n\tthis._str = this.getInputData(1);\r\n\tthis.setOutputData(1, this._obj);\r\n};\r\n\r\nJSONParse.prototype.onAction = function(name) {\r\n\tif(name == \"parse\")\r\n\t\tthis.parse();\r\n}\r\n\r\nLiteGraph.registerNodeType(\"basic/jsonparse\", JSONParse);\t\r\n\r\n\t//to store json objects\r\n    function ConstantData() {\r\n        this.addOutput(\"data\", \"object\");\r\n        this.addProperty(\"value\", \"\");\r\n        this.widget = this.addWidget(\"text\",\"json\",\"\",\"value\");\r\n        this.widgets_up = true;\r\n        this.size = [140, 30];\r\n        this._value = null;\r\n    }\r\n\r\n    ConstantData.title = \"Const Data\";\r\n    ConstantData.desc = \"Constant Data\";\r\n\r\n    ConstantData.prototype.onPropertyChanged = function(name, value) {\r\n        this.widget.value = value;\r\n        if (value == null || value == \"\") {\r\n            return;\r\n        }\r\n\r\n        try {\r\n            this._value = JSON.parse(value);\r\n            this.boxcolor = \"#AEA\";\r\n        } catch (err) {\r\n            this.boxcolor = \"red\";\r\n        }\r\n    };\r\n\r\n    ConstantData.prototype.onExecute = function() {\r\n        this.setOutputData(0, this._value);\r\n    };\r\n\r\n\tConstantData.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n    LiteGraph.registerNodeType(\"basic/data\", ConstantData);\r\n\r\n\t//to store json objects\r\n    function ConstantArray() {\r\n\t\tthis._value = [];\r\n        this.addInput(\"json\", \"\");\r\n        this.addOutput(\"arrayOut\", \"array\");\r\n\t\tthis.addOutput(\"length\", \"number\");\r\n        this.addProperty(\"value\", \"[]\");\r\n        this.widget = this.addWidget(\"text\",\"array\",this.properties.value,\"value\");\r\n        this.widgets_up = true;\r\n        this.size = [140, 50];\r\n    }\r\n\r\n    ConstantArray.title = \"Const Array\";\r\n    ConstantArray.desc = \"Constant Array\";\r\n\r\n    ConstantArray.prototype.onPropertyChanged = function(name, value) {\r\n        this.widget.value = value;\r\n        if (value == null || value == \"\") {\r\n            return;\r\n        }\r\n\r\n        try {\r\n\t\t\tif(value[0] != \"[\")\r\n\t            this._value = JSON.parse(\"[\" + value + \"]\");\r\n\t\t\telse\r\n\t            this._value = JSON.parse(value);\r\n            this.boxcolor = \"#AEA\";\r\n        } catch (err) {\r\n            this.boxcolor = \"red\";\r\n        }\r\n    };\r\n\r\n    ConstantArray.prototype.onExecute = function() {\r\n        var v = this.getInputData(0);\r\n\t\tif(v && v.length) //clone\r\n\t\t{\r\n\t\t\tif(!this._value)\r\n\t\t\t\tthis._value = new Array();\r\n\t\t\tthis._value.length = v.length;\r\n\t\t\tfor(var i = 0; i < v.length; ++i)\r\n\t\t\t\tthis._value[i] = v[i];\r\n\t\t}\r\n\t\tthis.setOutputData(0, this._value);\r\n\t\tthis.setOutputData(1, this._value ? ( this._value.length || 0) : 0 );\r\n    };\r\n\r\n\tConstantArray.prototype.setValue = ConstantNumber.prototype.setValue;\r\n\r\n    LiteGraph.registerNodeType(\"basic/array\", ConstantArray);\r\n\r\n\tfunction SetArray()\r\n\t{\r\n        this.addInput(\"arr\", \"array\");\r\n        this.addInput(\"value\", \"\");\r\n        this.addOutput(\"arr\", \"array\");\r\n\t\tthis.properties = { index: 0 };\r\n        this.widget = this.addWidget(\"number\",\"i\",this.properties.index,\"index\",{precision: 0, step: 10, min: 0});\r\n\t}\r\n\r\n    SetArray.title = \"Set Array\";\r\n    SetArray.desc = \"Sets index of array\";\r\n\r\n    SetArray.prototype.onExecute = function() {\r\n        var arr = this.getInputData(0);\r\n\t\tif(!arr)\r\n\t\t\treturn;\r\n        var v = this.getInputData(1);\r\n\t\tif(v === undefined )\r\n\t\t\treturn;\r\n\t\tif(this.properties.index)\r\n\t\t\tarr[ Math.floor(this.properties.index) ] = v;\r\n\t\tthis.setOutputData(0,arr);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/set_array\", SetArray );\r\n\r\n    function ArrayElement() {\r\n        this.addInput(\"array\", \"array,table,string\");\r\n        this.addInput(\"index\", \"number\");\r\n        this.addOutput(\"value\", \"\");\r\n\t\tthis.addProperty(\"index\",0);\r\n    }\r\n\r\n    ArrayElement.title = \"Array[i]\";\r\n    ArrayElement.desc = \"Returns an element from an array\";\r\n\r\n    ArrayElement.prototype.onExecute = function() {\r\n        var array = this.getInputData(0);\r\n        var index = this.getInputData(1);\r\n\t\tif(index == null)\r\n\t\t\tindex = this.properties.index;\r\n\t\tif(array == null || index == null )\r\n\t\t\treturn;\r\n        this.setOutputData(0, array[Math.floor(Number(index))] );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/array[]\", ArrayElement);\r\n\r\n    function TableElement() {\r\n        this.addInput(\"table\", \"table\");\r\n        this.addInput(\"row\", \"number\");\r\n        this.addInput(\"col\", \"number\");\r\n        this.addOutput(\"value\", \"\");\r\n\t\tthis.addProperty(\"row\",0);\r\n\t\tthis.addProperty(\"column\",0);\r\n    }\r\n\r\n    TableElement.title = \"Table[row][col]\";\r\n    TableElement.desc = \"Returns an element from a table\";\r\n\r\n    TableElement.prototype.onExecute = function() {\r\n        var table = this.getInputData(0);\r\n        var row = this.getInputData(1);\r\n        var col = this.getInputData(2);\r\n\t\tif(row == null)\r\n\t\t\trow = this.properties.row;\r\n\t\tif(col == null)\r\n\t\t\tcol = this.properties.column;\r\n\t\tif(table == null || row == null || col == null)\r\n\t\t\treturn;\r\n\t\tvar row = table[Math.floor(Number(row))];\r\n\t\tif(row)\r\n\t        this.setOutputData(0, row[Math.floor(Number(col))] );\r\n\t\telse\r\n\t        this.setOutputData(0, null );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/table[][]\", TableElement);\r\n\r\n    function ObjectProperty() {\r\n        this.addInput(\"obj\", \"object\");\r\n        this.addOutput(\"property\", 0);\r\n        this.addProperty(\"value\", 0);\r\n        this.widget = this.addWidget(\"text\",\"prop.\",\"\",this.setValue.bind(this) );\r\n        this.widgets_up = true;\r\n        this.size = [140, 30];\r\n        this._value = null;\r\n    }\r\n\r\n    ObjectProperty.title = \"Object property\";\r\n    ObjectProperty.desc = \"Outputs the property of an object\";\r\n\r\n    ObjectProperty.prototype.setValue = function(v) {\r\n        this.properties.value = v;\r\n        this.widget.value = v;\r\n    };\r\n\r\n    ObjectProperty.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return \"in.\" + this.properties.value;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    ObjectProperty.prototype.onPropertyChanged = function(name, value) {\r\n        this.widget.value = value;\r\n    };\r\n\r\n    ObjectProperty.prototype.onExecute = function() {\r\n        var data = this.getInputData(0);\r\n        if (data != null) {\r\n            this.setOutputData(0, data[this.properties.value]);\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/object_property\", ObjectProperty);\r\n\r\n    function ObjectKeys() {\r\n        this.addInput(\"obj\", \"\");\r\n        this.addOutput(\"keys\", \"array\");\r\n        this.size = [140, 30];\r\n    }\r\n\r\n    ObjectKeys.title = \"Object keys\";\r\n    ObjectKeys.desc = \"Outputs an array with the keys of an object\";\r\n\r\n    ObjectKeys.prototype.onExecute = function() {\r\n        var data = this.getInputData(0);\r\n        if (data != null) {\r\n            this.setOutputData(0, Object.keys(data) );\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/object_keys\", ObjectKeys);\r\n\r\n\r\n\tfunction SetObject()\r\n\t{\r\n        this.addInput(\"obj\", \"\");\r\n        this.addInput(\"value\", \"\");\r\n        this.addOutput(\"obj\", \"\");\r\n\t\tthis.properties = { property: \"\" };\r\n        this.name_widget = this.addWidget(\"text\",\"prop.\",this.properties.property,\"property\");\r\n\t}\r\n\r\n    SetObject.title = \"Set Object\";\r\n    SetObject.desc = \"Adds propertiesrty to object\";\r\n\r\n    SetObject.prototype.onExecute = function() {\r\n        var obj = this.getInputData(0);\r\n\t\tif(!obj)\r\n\t\t\treturn;\r\n        var v = this.getInputData(1);\r\n\t\tif(v === undefined )\r\n\t\t\treturn;\r\n\t\tif(this.properties.property)\r\n\t\t\tobj[ this.properties.property ] = v;\r\n\t\tthis.setOutputData(0,obj);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/set_object\", SetObject );\r\n\r\n\r\n    function MergeObjects() {\r\n        this.addInput(\"A\", \"object\");\r\n        this.addInput(\"B\", \"object\");\r\n        this.addOutput(\"out\", \"object\");\r\n\t\tthis._result = {};\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"clear\",\"\",function(){\r\n\t\t\tthat._result = {};\r\n\t\t});\r\n\t\tthis.size = this.computeSize();\r\n    }\r\n\r\n    MergeObjects.title = \"Merge Objects\";\r\n    MergeObjects.desc = \"Creates an object copying properties from others\";\r\n\r\n    MergeObjects.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        var B = this.getInputData(1);\r\n\t\tvar C = this._result;\r\n\t\tif(A)\r\n\t\t\tfor(var i in A)\r\n\t\t\t\tC[i] = A[i];\r\n\t\tif(B)\r\n\t\t\tfor(var i in B)\r\n\t\t\t\tC[i] = B[i];\r\n\t\tthis.setOutputData(0,C);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/merge_objects\", MergeObjects );\r\n\r\n    //Store as variable\r\n    function Variable() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"in\");\r\n        this.addOutput(\"out\");\r\n\t\tthis.properties = { varname: \"myname\", container: Variable.LITEGRAPH };\r\n        this.value = null;\r\n    }\r\n\r\n    Variable.title = \"Variable\";\r\n    Variable.desc = \"store/read variable value\";\r\n\r\n\tVariable.LITEGRAPH = 0; //between all graphs\r\n\tVariable.GRAPH = 1;\t//only inside this graph\r\n\tVariable.GLOBALSCOPE = 2;\t//attached to Window\r\n\r\n    Variable[\"@container\"] = { type: \"enum\", values: {\"litegraph\":Variable.LITEGRAPH, \"graph\":Variable.GRAPH,\"global\": Variable.GLOBALSCOPE} };\r\n\r\n    Variable.prototype.onExecute = function() {\r\n\t\tvar container = this.getContainer();\r\n\r\n\t\tif(this.isInputConnected(0))\r\n\t\t{\r\n\t\t\tthis.value = this.getInputData(0);\r\n\t\t\tcontainer[ this.properties.varname ] = this.value;\r\n\t\t\tthis.setOutputData(0, this.value );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData( 0, container[ this.properties.varname ] );\r\n    };\r\n\r\n\tVariable.prototype.getContainer = function()\r\n\t{\r\n\t\tswitch(this.properties.container)\r\n\t\t{\r\n\t\t\tcase Variable.GRAPH:\r\n\t\t\t\tif(this.graph)\r\n\t\t\t\t\treturn this.graph.vars;\r\n\t\t\t\treturn {};\r\n\t\t\t\tbreak;\r\n\t\t\tcase Variable.GLOBALSCOPE:\r\n\t\t\t\treturn global;\r\n\t\t\t\tbreak;\r\n\t\t\tcase Variable.LITEGRAPH:\r\n\t\t\tdefault:\r\n\t\t\t\treturn LiteGraph.Globals;\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n    Variable.prototype.getTitle = function() {\r\n        return this.properties.varname;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/variable\", Variable);\r\n\r\n    function length(v) {\r\n        if(v && v.length != null)\r\n\t\t\treturn Number(v.length);\r\n\t\treturn 0;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"basic/length\",\r\n        length,\r\n        [\"\"],\r\n        \"number\"\r\n    );\r\n\r\n    function length(v) {\r\n        if(v && v.length != null)\r\n\t\t\treturn Number(v.length);\r\n\t\treturn 0;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"basic/not\",\r\n        function(a){ return !a; },\r\n        [\"\"],\r\n        \"boolean\"\r\n    );\r\n\r\n\tfunction DownloadData() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"data\", 0 );\r\n        this.addInput(\"download\", LiteGraph.ACTION );\r\n\t\tthis.properties = { filename: \"data.json\" };\r\n        this.value = null;\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"Download\",\"\", function(v){\r\n\t\t\tif(!that.value)\r\n\t\t\t\treturn;\r\n\t\t\tthat.downloadAsFile();\r\n\t\t});\r\n    }\r\n\r\n    DownloadData.title = \"Download\";\r\n    DownloadData.desc = \"Download some data\";\r\n\r\n\tDownloadData.prototype.downloadAsFile = function()\r\n\t{\r\n\t\tif(this.value == null)\r\n\t\t\treturn;\r\n\r\n\t\tvar str = null;\r\n\t\tif(this.value.constructor === String)\r\n\t\t\tstr = this.value;\r\n\t\telse\r\n\t\t\tstr = JSON.stringify(this.value);\r\n\r\n\t\tvar file = new Blob([str]);\r\n\t\tvar url = URL.createObjectURL( file );\r\n\t\tvar element = document.createElement(\"a\");\r\n\t\telement.setAttribute('href', url);\r\n\t\telement.setAttribute('download', this.properties.filename );\r\n\t\telement.style.display = 'none';\r\n\t\tdocument.body.appendChild(element);\r\n\t\telement.click();\r\n\t\tdocument.body.removeChild(element);\r\n\t\tsetTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url\r\n\t}\r\n\r\n    DownloadData.prototype.onAction = function(action, param) {\r\n\t\tvar that = this;\r\n\t\tsetTimeout( function(){ that.downloadAsFile(); }, 100); //deferred to avoid blocking the renderer with the popup\r\n\t}\r\n\r\n    DownloadData.prototype.onExecute = function() {\r\n        if (this.inputs[0]) {\r\n            this.value = this.getInputData(0);\r\n        }\r\n    };\r\n\r\n    DownloadData.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.properties.filename;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/download\", DownloadData);\r\n\r\n\r\n\r\n    //Watch a value in the editor\r\n    function Watch() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"value\", 0, { label: \"\" });\r\n        this.value = 0;\r\n    }\r\n\r\n    Watch.title = \"Watch\";\r\n    Watch.desc = \"Show value of input\";\r\n\r\n    Watch.prototype.onExecute = function() {\r\n        if (this.inputs[0]) {\r\n            this.value = this.getInputData(0);\r\n        }\r\n    };\r\n\r\n    Watch.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this.inputs[0].label;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    Watch.toString = function(o) {\r\n        if (o == null) {\r\n            return \"null\";\r\n        } else if (o.constructor === Number) {\r\n            return o.toFixed(3);\r\n        } else if (o.constructor === Array) {\r\n            var str = \"[\";\r\n            for (var i = 0; i < o.length; ++i) {\r\n                str += Watch.toString(o[i]) + (i + 1 != o.length ? \",\" : \"\");\r\n            }\r\n            str += \"]\";\r\n            return str;\r\n        } else {\r\n            return String(o);\r\n        }\r\n    };\r\n\r\n    Watch.prototype.onDrawBackground = function(ctx) {\r\n        //show the current value\r\n        this.inputs[0].label = Watch.toString(this.value);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/watch\", Watch);\r\n\r\n    //in case one type doesnt match other type but you want to connect them anyway\r\n    function Cast() {\r\n        this.addInput(\"in\", 0);\r\n        this.addOutput(\"out\", 0);\r\n        this.size = [40, 30];\r\n    }\r\n\r\n    Cast.title = \"Cast\";\r\n    Cast.desc = \"Allows to connect different types\";\r\n\r\n    Cast.prototype.onExecute = function() {\r\n        this.setOutputData(0, this.getInputData(0));\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/cast\", Cast);\r\n\r\n    //Show value inside the debug console\r\n    function Console() {\r\n        this.mode = LiteGraph.ON_EVENT;\r\n        this.size = [80, 30];\r\n        this.addProperty(\"msg\", \"\");\r\n        this.addInput(\"log\", LiteGraph.EVENT);\r\n        this.addInput(\"msg\", 0);\r\n    }\r\n\r\n    Console.title = \"Console\";\r\n    Console.desc = \"Show value inside the console\";\r\n\r\n    Console.prototype.onAction = function(action, param) {\r\n        // param is the action\r\n        var msg = this.getInputData(1); //getInputDataByName(\"msg\");\r\n        //if (msg == null || typeof msg == \"undefined\") return;\r\n        if (!msg) msg = this.properties.msg;\r\n        if (!msg) msg = \"Event: \"+param; // msg is undefined if the slot is lost?\r\n        if (action == \"log\") {\r\n            console.log(msg);\r\n        } else if (action == \"warn\") {\r\n            console.warn(msg);\r\n        } else if (action == \"error\") {\r\n            console.error(msg);\r\n        }\r\n    };\r\n\r\n    Console.prototype.onExecute = function() {\r\n        var msg = this.getInputData(1); //getInputDataByName(\"msg\");\r\n        if (!msg) msg = this.properties.msg;\r\n        if (msg != null && typeof msg != \"undefined\") {\r\n            this.properties.msg = msg;\r\n            console.log(msg);\r\n        }\r\n    };\r\n\r\n    Console.prototype.onGetInputs = function() {\r\n        return [\r\n            [\"log\", LiteGraph.ACTION],\r\n            [\"warn\", LiteGraph.ACTION],\r\n            [\"error\", LiteGraph.ACTION]\r\n        ];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/console\", Console);\r\n\r\n    //Show value inside the debug console\r\n    function Alert() {\r\n        this.mode = LiteGraph.ON_EVENT;\r\n        this.addProperty(\"msg\", \"\");\r\n        this.addInput(\"\", LiteGraph.EVENT);\r\n        var that = this;\r\n        this.widget = this.addWidget(\"text\", \"Text\", \"\", \"msg\");\r\n        this.widgets_up = true;\r\n        this.size = [200, 30];\r\n    }\r\n\r\n    Alert.title = \"Alert\";\r\n    Alert.desc = \"Show an alert window\";\r\n    Alert.color = \"#510\";\r\n\r\n    Alert.prototype.onConfigure = function(o) {\r\n        this.widget.value = o.properties.msg;\r\n    };\r\n\r\n    Alert.prototype.onAction = function(action, param) {\r\n        var msg = this.properties.msg;\r\n        setTimeout(function() {\r\n            alert(msg);\r\n        }, 10);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/alert\", Alert);\r\n\r\n    //Execites simple code\r\n    function NodeScript() {\r\n        this.size = [60, 30];\r\n        this.addProperty(\"onExecute\", \"return A;\");\r\n        this.addInput(\"A\", 0);\r\n        this.addInput(\"B\", 0);\r\n        this.addOutput(\"out\", 0);\r\n\r\n        this._func = null;\r\n        this.data = {};\r\n    }\r\n\r\n    NodeScript.prototype.onConfigure = function(o) {\r\n        if (o.properties.onExecute && LiteGraph.allow_scripts)\r\n            this.compileCode(o.properties.onExecute);\r\n\t\telse\r\n\t\t\tconsole.warn(\"Script not compiled, LiteGraph.allow_scripts is false\");\r\n    };\r\n\r\n    NodeScript.title = \"Script\";\r\n    NodeScript.desc = \"executes a code (max 256 characters)\";\r\n\r\n    NodeScript.widgets_info = {\r\n        onExecute: { type: \"code\" }\r\n    };\r\n\r\n    NodeScript.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"onExecute\" && LiteGraph.allow_scripts)\r\n            this.compileCode(value);\r\n\t\telse\r\n\t\t\tconsole.warn(\"Script not compiled, LiteGraph.allow_scripts is false\");\r\n    };\r\n\r\n    NodeScript.prototype.compileCode = function(code) {\r\n        this._func = null;\r\n        if (code.length > 256) {\r\n            console.warn(\"Script too long, max 256 chars\");\r\n        } else {\r\n            var code_low = code.toLowerCase();\r\n            var forbidden_words = [\r\n                \"script\",\r\n                \"body\",\r\n                \"document\",\r\n                \"eval\",\r\n                \"nodescript\",\r\n                \"function\"\r\n            ]; //bad security solution\r\n            for (var i = 0; i < forbidden_words.length; ++i) {\r\n                if (code_low.indexOf(forbidden_words[i]) != -1) {\r\n                    console.warn(\"invalid script\");\r\n                    return;\r\n                }\r\n            }\r\n            try {\r\n                this._func = new Function(\"A\", \"B\", \"C\", \"DATA\", \"node\", code);\r\n            } catch (err) {\r\n                console.error(\"Error parsing script\");\r\n                console.error(err);\r\n            }\r\n        }\r\n    };\r\n\r\n    NodeScript.prototype.onExecute = function() {\r\n        if (!this._func) {\r\n            return;\r\n        }\r\n\r\n        try {\r\n            var A = this.getInputData(0);\r\n            var B = this.getInputData(1);\r\n            var C = this.getInputData(2);\r\n            this.setOutputData(0, this._func(A, B, C, this.data, this));\r\n        } catch (err) {\r\n            console.error(\"Error in script\");\r\n            console.error(err);\r\n        }\r\n    };\r\n\r\n    NodeScript.prototype.onGetOutputs = function() {\r\n        return [[\"C\", \"\"]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/script\", NodeScript);\r\n    \r\n    \r\n    function GenericCompare() {\r\n        this.addInput(\"A\", 0);\r\n        this.addInput(\"B\", 0);\r\n        this.addOutput(\"true\", \"boolean\");\r\n        this.addOutput(\"false\", \"boolean\");\r\n        this.addProperty(\"A\", 1);\r\n        this.addProperty(\"B\", 1);\r\n        this.addProperty(\"OP\", \"==\", \"enum\", { values: GenericCompare.values });\r\n\t\tthis.addWidget(\"combo\",\"Op.\",this.properties.OP,{ property: \"OP\", values: GenericCompare.values } );\r\n\r\n        this.size = [80, 60];\r\n    }\r\n\r\n    GenericCompare.values = [\"==\", \"!=\"]; //[\">\", \"<\", \"==\", \"!=\", \"<=\", \">=\", \"||\", \"&&\" ];\r\n    GenericCompare[\"@OP\"] = {\r\n        type: \"enum\",\r\n        title: \"operation\",\r\n        values: GenericCompare.values\r\n    };\r\n\r\n    GenericCompare.title = \"Compare *\";\r\n    GenericCompare.desc = \"evaluates condition between A and B\";\r\n\r\n    GenericCompare.prototype.getTitle = function() {\r\n        return \"*A \" + this.properties.OP + \" *B\";\r\n    };\r\n\r\n    GenericCompare.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        if (A === undefined) {\r\n            A = this.properties.A;\r\n        } else {\r\n            this.properties.A = A;\r\n        }\r\n\r\n        var B = this.getInputData(1);\r\n        if (B === undefined) {\r\n            B = this.properties.B;\r\n        } else {\r\n            this.properties.B = B;\r\n        }\r\n\r\n        var result = false;\r\n        if (typeof A == typeof B){\r\n            switch (this.properties.OP) {\r\n                case \"==\":\r\n                case \"!=\":\r\n                    // traverse both objects.. consider that this is not a true deep check! consider underscore or other library for thath :: _isEqual()\r\n                    result = true;\r\n                    switch(typeof A){\r\n                        case \"object\":\r\n                            var aProps = Object.getOwnPropertyNames(A);\r\n                            var bProps = Object.getOwnPropertyNames(B);\r\n                            if (aProps.length != bProps.length){\r\n                                result = false;\r\n                                break;\r\n                            }\r\n                            for (var i = 0; i < aProps.length; i++) {\r\n                                var propName = aProps[i];\r\n                                if (A[propName] !== B[propName]) {\r\n                                    result = false;\r\n                                    break;\r\n                                }\r\n                            }\r\n                        break;\r\n                        default:\r\n                            result = A == B;\r\n                    }\r\n                    if (this.properties.OP == \"!=\") result = !result;\r\n                    break;\r\n                /*case \">\":\r\n                    result = A > B;\r\n                    break;\r\n                case \"<\":\r\n                    result = A < B;\r\n                    break;\r\n                case \"<=\":\r\n                    result = A <= B;\r\n                    break;\r\n                case \">=\":\r\n                    result = A >= B;\r\n                    break;\r\n                case \"||\":\r\n                    result = A || B;\r\n                    break;\r\n                case \"&&\":\r\n                    result = A && B;\r\n                    break;*/\r\n            }\r\n        }\r\n        this.setOutputData(0, result);\r\n        this.setOutputData(1, !result);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"basic/CompareValues\", GenericCompare);\r\n    \r\n})(this);\r\n"
  },
  {
    "path": "src/nodes/events.js",
    "content": "//event related nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    //Show value inside the debug console\r\n    function LogEvent() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"event\", LiteGraph.ACTION);\r\n    }\r\n\r\n    LogEvent.title = \"Log Event\";\r\n    LogEvent.desc = \"Log event in console\";\r\n\r\n    LogEvent.prototype.onAction = function(action, param, options) {\r\n        console.log(action, param);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/log\", LogEvent);\r\n\r\n    //convert to Event if the value is true\r\n    function TriggerEvent() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"if\", \"\");\r\n        this.addOutput(\"true\", LiteGraph.EVENT);\r\n        this.addOutput(\"change\", LiteGraph.EVENT);\r\n        this.addOutput(\"false\", LiteGraph.EVENT);\r\n\t\tthis.properties = { only_on_change: true };\r\n\t\tthis.prev = 0;\r\n    }\r\n\r\n    TriggerEvent.title = \"TriggerEvent\";\r\n    TriggerEvent.desc = \"Triggers event if input evaluates to true\";\r\n\r\n    TriggerEvent.prototype.onExecute = function( param, options) {\r\n\t\tvar v = this.getInputData(0);\r\n\t\tvar changed = (v != this.prev);\r\n\t\tif(this.prev === 0)\r\n\t\t\tchanged = false;\r\n\t\tvar must_resend = (changed && this.properties.only_on_change) || (!changed && !this.properties.only_on_change);\r\n\t\tif(v && must_resend )\r\n\t        this.triggerSlot(0, param, null, options);\r\n\t\tif(!v && must_resend)\r\n\t        this.triggerSlot(2, param, null, options);\r\n\t\tif(changed)\r\n\t        this.triggerSlot(1, param, null, options);\r\n\t\tthis.prev = v;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/trigger\", TriggerEvent);\r\n\r\n    //Sequence of events\r\n    function Sequence() {\r\n\t\tvar that = this;\r\n        this.addInput(\"\", LiteGraph.ACTION);\r\n        this.addInput(\"\", LiteGraph.ACTION);\r\n        this.addInput(\"\", LiteGraph.ACTION);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addWidget(\"button\",\"+\",null,function(){\r\n\t        that.addInput(\"\", LiteGraph.ACTION);\r\n\t        that.addOutput(\"\", LiteGraph.EVENT);\r\n        });\r\n        this.size = [90, 70];\r\n        this.flags = { horizontal: true, render_box: false };\r\n    }\r\n\r\n    Sequence.title = \"Sequence\";\r\n    Sequence.desc = \"Triggers a sequence of events when an event arrives\";\r\n\r\n    Sequence.prototype.getTitle = function() {\r\n        return \"\";\r\n    };\r\n\r\n    Sequence.prototype.onAction = function(action, param, options) {\r\n        if (this.outputs) {\r\n            options = options || {};\r\n            for (var i = 0; i < this.outputs.length; ++i) {\r\n\t\t\t\tvar output = this.outputs[i];\r\n\t\t\t\t//needs more info about this...\r\n\t\t\t\tif( options.action_call ) // CREATE A NEW ID FOR THE ACTION\r\n\t                options.action_call = options.action_call + \"_seq_\" + i;\r\n\t\t\t\telse\r\n\t\t\t\t\toptions.action_call = this.id + \"_\" + (action ? action : \"action\")+\"_seq_\"+i+\"_\"+Math.floor(Math.random()*9999);\r\n                this.triggerSlot(i, param, null, options);\r\n            }\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/sequence\", Sequence);\r\n\r\n\r\n   //Sequence of events\r\n   function WaitAll() {\r\n    var that = this;\r\n    this.addInput(\"\", LiteGraph.ACTION);\r\n    this.addInput(\"\", LiteGraph.ACTION);\r\n    this.addOutput(\"\", LiteGraph.EVENT);\r\n    this.addWidget(\"button\",\"+\",null,function(){\r\n        that.addInput(\"\", LiteGraph.ACTION);\r\n        that.size[0] = 90;\r\n    });\r\n    this.size = [90, 70];\r\n    this.ready = [];\r\n}\r\n\r\nWaitAll.title = \"WaitAll\";\r\nWaitAll.desc = \"Wait until all input events arrive then triggers output\";\r\n\r\nWaitAll.prototype.getTitle = function() {\r\n    return \"\";\r\n};\r\n\r\nWaitAll.prototype.onDrawBackground = function(ctx)\r\n{\r\n    if (this.flags.collapsed) {\r\n        return;\r\n    }\r\n    for(var i = 0; i < this.inputs.length; ++i)\r\n    {\r\n        var y = i * LiteGraph.NODE_SLOT_HEIGHT + 10;\r\n        ctx.fillStyle = this.ready[i] ? \"#AFB\" : \"#000\";\r\n        ctx.fillRect(20, y, 10, 10);\r\n    }\r\n}\r\n\r\nWaitAll.prototype.onAction = function(action, param, options, slot_index) {\r\n    if(slot_index == null)\r\n        return;\r\n\r\n    //check all\r\n    this.ready.length = this.outputs.length;\r\n    this.ready[slot_index] = true;\r\n    for(var i = 0; i < this.ready.length;++i)\r\n        if(!this.ready[i])\r\n            return;\r\n    //pass\r\n    this.reset();\r\n    this.triggerSlot(0);\r\n};\r\n\r\nWaitAll.prototype.reset = function()\r\n{\r\n    this.ready.length = 0;\r\n}\r\n\r\nLiteGraph.registerNodeType(\"events/waitAll\", WaitAll);    \r\n\r\n\r\n    //Sequencer for events\r\n    function Stepper() {\r\n\t\tvar that = this;\r\n\t\tthis.properties = { index: 0 };\r\n        this.addInput(\"index\", \"number\");\r\n        this.addInput(\"step\", LiteGraph.ACTION);\r\n        this.addInput(\"reset\", LiteGraph.ACTION);\r\n        this.addOutput(\"index\", \"number\");\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT);\r\n        this.addOutput(\"\", LiteGraph.EVENT,{removable:true});\r\n        this.addWidget(\"button\",\"+\",null,function(){\r\n\t        that.addOutput(\"\", LiteGraph.EVENT, {removable:true});\r\n        });\r\n        this.size = [120, 120];\r\n        this.flags = { render_box: false };\r\n    }\r\n\r\n    Stepper.title = \"Stepper\";\r\n    Stepper.desc = \"Trigger events sequentially when an tick arrives\";\r\n\r\n\tStepper.prototype.onDrawBackground = function(ctx)\r\n\t{\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\t\tvar index = this.properties.index || 0;\r\n        ctx.fillStyle = \"#AFB\";\r\n\t\tvar w = this.size[0];\r\n        var y = (index + 1)* LiteGraph.NODE_SLOT_HEIGHT + 4;\r\n        ctx.beginPath();\r\n        ctx.moveTo(w - 30, y);\r\n        ctx.lineTo(w - 30, y + LiteGraph.NODE_SLOT_HEIGHT);\r\n        ctx.lineTo(w - 15, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5);\r\n        ctx.fill();\r\n\t}\r\n\r\n\tStepper.prototype.onExecute = function()\r\n\t{\r\n\t\tvar index = this.getInputData(0);\r\n\t\tif(index != null)\r\n\t\t{\r\n\t\t\tindex = Math.floor(index);\r\n\t\t\tindex = clamp( index, 0, this.outputs ? (this.outputs.length - 2) : 0 );\r\n\t\t\tif( index != this.properties.index )\r\n\t\t\t{\r\n\t\t\t\tthis.properties.index = index;\r\n\t\t\t    this.triggerSlot( index+1 );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0, this.properties.index );\r\n\t}\r\n\r\n    Stepper.prototype.onAction = function(action, param) {\r\n\t\tif(action == \"reset\")\r\n\t\t\tthis.properties.index = 0;\r\n\t\telse if(action == \"step\")\r\n\t\t{\r\n            this.triggerSlot(this.properties.index+1, param);\r\n\t\t\tvar n = this.outputs ? this.outputs.length - 1 : 0;\r\n\t\t\tthis.properties.index = (this.properties.index + 1) % n;\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/stepper\", Stepper);\r\n\r\n    //Filter events\r\n    function FilterEvent() {\r\n        this.size = [60, 30];\r\n        this.addInput(\"event\", LiteGraph.ACTION);\r\n        this.addOutput(\"event\", LiteGraph.EVENT);\r\n        this.properties = {\r\n            equal_to: \"\",\r\n            has_property: \"\",\r\n            property_equal_to: \"\"\r\n        };\r\n    }\r\n\r\n    FilterEvent.title = \"Filter Event\";\r\n    FilterEvent.desc = \"Blocks events that do not match the filter\";\r\n\r\n    FilterEvent.prototype.onAction = function(action, param, options) {\r\n        if (param == null) {\r\n            return;\r\n        }\r\n\r\n        if (this.properties.equal_to && this.properties.equal_to != param) {\r\n            return;\r\n        }\r\n\r\n        if (this.properties.has_property) {\r\n            var prop = param[this.properties.has_property];\r\n            if (prop == null) {\r\n                return;\r\n            }\r\n\r\n            if (\r\n                this.properties.property_equal_to &&\r\n                this.properties.property_equal_to != prop\r\n            ) {\r\n                return;\r\n            }\r\n        }\r\n\r\n        this.triggerSlot(0, param, null, options);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/filter\", FilterEvent);\r\n\r\n\r\n    function EventBranch() {\r\n        this.addInput(\"in\", LiteGraph.ACTION);\r\n        this.addInput(\"cond\", \"boolean\");\r\n        this.addOutput(\"true\", LiteGraph.EVENT);\r\n        this.addOutput(\"false\", LiteGraph.EVENT);\r\n        this.size = [120, 60];\r\n\t\tthis._value = false;\r\n    }\r\n\r\n    EventBranch.title = \"Branch\";\r\n    EventBranch.desc = \"If condition is true, outputs triggers true, otherwise false\";\r\n\r\n    EventBranch.prototype.onExecute = function() {\r\n\t\tthis._value = this.getInputData(1);\r\n\t}\r\n\r\n    EventBranch.prototype.onAction = function(action, param, options) {\r\n        this._value = this.getInputData(1);\r\n\t\tthis.triggerSlot(this._value ? 0 : 1, param, null, options);\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"events/branch\", EventBranch);\r\n\r\n    //Show value inside the debug console\r\n    function EventCounter() {\r\n        this.addInput(\"inc\", LiteGraph.ACTION);\r\n        this.addInput(\"dec\", LiteGraph.ACTION);\r\n        this.addInput(\"reset\", LiteGraph.ACTION);\r\n        this.addOutput(\"change\", LiteGraph.EVENT);\r\n        this.addOutput(\"num\", \"number\");\r\n        this.addProperty(\"doCountExecution\", false, \"boolean\", {name: \"Count Executions\"});\r\n        this.addWidget(\"toggle\",\"Count Exec.\",this.properties.doCountExecution,\"doCountExecution\");\r\n        this.num = 0;\r\n    }\r\n\r\n    EventCounter.title = \"Counter\";\r\n    EventCounter.desc = \"Counts events\";\r\n\r\n    EventCounter.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return String(this.num);\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    EventCounter.prototype.onAction = function(action, param, options) {\r\n        var v = this.num;\r\n        if (action == \"inc\") {\r\n            this.num += 1;\r\n        } else if (action == \"dec\") {\r\n            this.num -= 1;\r\n        } else if (action == \"reset\") {\r\n            this.num = 0;\r\n        }\r\n        if (this.num != v) {\r\n            this.trigger(\"change\", this.num);\r\n        }\r\n    };\r\n\r\n    EventCounter.prototype.onDrawBackground = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n        ctx.fillStyle = \"#AAA\";\r\n        ctx.font = \"20px Arial\";\r\n        ctx.textAlign = \"center\";\r\n        ctx.fillText(this.num, this.size[0] * 0.5, this.size[1] * 0.5);\r\n    };\r\n\r\n    EventCounter.prototype.onExecute = function() {\r\n        if(this.properties.doCountExecution){\r\n            this.num += 1;\r\n        }\r\n        this.setOutputData(1, this.num);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/counter\", EventCounter);\r\n\r\n    //Show value inside the debug console\r\n    function DelayEvent() {\r\n        this.size = [60, 30];\r\n        this.addProperty(\"time_in_ms\", 1000);\r\n        this.addInput(\"event\", LiteGraph.ACTION);\r\n        this.addOutput(\"on_time\", LiteGraph.EVENT);\r\n\r\n        this._pending = [];\r\n    }\r\n\r\n    DelayEvent.title = \"Delay\";\r\n    DelayEvent.desc = \"Delays one event\";\r\n\r\n    DelayEvent.prototype.onAction = function(action, param, options) {\r\n        var time = this.properties.time_in_ms;\r\n        if (time <= 0) {\r\n            this.trigger(null, param, options);\r\n        } else {\r\n            this._pending.push([time, param]);\r\n        }\r\n    };\r\n\r\n    DelayEvent.prototype.onExecute = function(param, options) {\r\n        var dt = this.graph.elapsed_time * 1000; //in ms\r\n\r\n        if (this.isInputConnected(1)) {\r\n            this.properties.time_in_ms = this.getInputData(1);\r\n        }\r\n\r\n        for (var i = 0; i < this._pending.length; ++i) {\r\n            var actionPass = this._pending[i];\r\n            actionPass[0] -= dt;\r\n            if (actionPass[0] > 0) {\r\n                continue;\r\n            }\r\n\r\n            //remove\r\n            this._pending.splice(i, 1);\r\n            --i;\r\n\r\n            //trigger\r\n            this.trigger(null, actionPass[1], options);\r\n        }\r\n    };\r\n\r\n    DelayEvent.prototype.onGetInputs = function() {\r\n        return [[\"event\", LiteGraph.ACTION], [\"time_in_ms\", \"number\"]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/delay\", DelayEvent);\r\n\r\n    //Show value inside the debug console\r\n    function TimerEvent() {\r\n        this.addProperty(\"interval\", 1000);\r\n        this.addProperty(\"event\", \"tick\");\r\n        this.addOutput(\"on_tick\", LiteGraph.EVENT);\r\n        this.time = 0;\r\n        this.last_interval = 1000;\r\n        this.triggered = false;\r\n    }\r\n\r\n    TimerEvent.title = \"Timer\";\r\n    TimerEvent.desc = \"Sends an event every N milliseconds\";\r\n\r\n    TimerEvent.prototype.onStart = function() {\r\n        this.time = 0;\r\n    };\r\n\r\n    TimerEvent.prototype.getTitle = function() {\r\n        return \"Timer: \" + this.last_interval.toString() + \"ms\";\r\n    };\r\n\r\n    TimerEvent.on_color = \"#AAA\";\r\n    TimerEvent.off_color = \"#222\";\r\n\r\n    TimerEvent.prototype.onDrawBackground = function() {\r\n        this.boxcolor = this.triggered\r\n            ? TimerEvent.on_color\r\n            : TimerEvent.off_color;\r\n        this.triggered = false;\r\n    };\r\n\r\n    TimerEvent.prototype.onExecute = function() {\r\n        var dt = this.graph.elapsed_time * 1000; //in ms\r\n\r\n        var trigger = this.time == 0;\r\n\r\n        this.time += dt;\r\n        this.last_interval = Math.max(\r\n            1,\r\n            this.getInputOrProperty(\"interval\") | 0\r\n        );\r\n\r\n        if (\r\n            !trigger &&\r\n            (this.time < this.last_interval || isNaN(this.last_interval))\r\n        ) {\r\n            if (this.inputs && this.inputs.length > 1 && this.inputs[1]) {\r\n                this.setOutputData(1, false);\r\n            }\r\n            return;\r\n        }\r\n\r\n        this.triggered = true;\r\n        this.time = this.time % this.last_interval;\r\n        this.trigger(\"on_tick\", this.properties.event);\r\n        if (this.inputs && this.inputs.length > 1 && this.inputs[1]) {\r\n            this.setOutputData(1, true);\r\n        }\r\n    };\r\n\r\n    TimerEvent.prototype.onGetInputs = function() {\r\n        return [[\"interval\", \"number\"]];\r\n    };\r\n\r\n    TimerEvent.prototype.onGetOutputs = function() {\r\n        return [[\"tick\", \"boolean\"]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/timer\", TimerEvent);\r\n\r\n\r\n\r\n    function SemaphoreEvent() {\r\n        this.addInput(\"go\", LiteGraph.ACTION );\r\n        this.addInput(\"green\", LiteGraph.ACTION );\r\n        this.addInput(\"red\", LiteGraph.ACTION );\r\n        this.addOutput(\"continue\", LiteGraph.EVENT );\r\n        this.addOutput(\"blocked\", LiteGraph.EVENT );\r\n        this.addOutput(\"is_green\", \"boolean\" );\r\n\t\tthis._ready = false;\r\n\t\tthis.properties = {};\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"reset\",\"\",function(){\r\n\t\t\tthat._ready = false;\r\n\t\t});\r\n    }\r\n\r\n    SemaphoreEvent.title = \"Semaphore Event\";\r\n    SemaphoreEvent.desc = \"Until both events are not triggered, it doesnt continue.\";\r\n\r\n\tSemaphoreEvent.prototype.onExecute = function()\r\n\t{\r\n\t\tthis.setOutputData(1,this._ready);\r\n\t\tthis.boxcolor = this._ready ? \"#9F9\" : \"#FA5\";\r\n\t}\r\n\r\n    SemaphoreEvent.prototype.onAction = function(action, param) {\r\n\t\tif( action == \"go\" )\r\n\t\t\tthis.triggerSlot( this._ready ? 0 : 1 );\r\n\t\telse if( action == \"green\" )\r\n\t\t\tthis._ready = true;\r\n\t\telse if( action == \"red\" )\r\n\t\t\tthis._ready = false;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/semaphore\", SemaphoreEvent);\r\n\r\n    function OnceEvent() {\r\n        this.addInput(\"in\", LiteGraph.ACTION );\r\n        this.addInput(\"reset\", LiteGraph.ACTION );\r\n        this.addOutput(\"out\", LiteGraph.EVENT );\r\n\t\tthis._once = false;\r\n\t\tthis.properties = {};\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"reset\",\"\",function(){\r\n\t\t\tthat._once = false;\r\n\t\t});\r\n    }\r\n\r\n    OnceEvent.title = \"Once\";\r\n    OnceEvent.desc = \"Only passes an event once, then gets locked\";\r\n\r\n    OnceEvent.prototype.onAction = function(action, param) {\r\n\t\tif( action == \"in\" && !this._once )\r\n\t\t{\r\n\t\t\tthis._once = true;\r\n\t\t\tthis.triggerSlot( 0, param );\r\n\t\t}\r\n\t\telse if( action == \"reset\" )\r\n\t\t\tthis._once = false;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"events/once\", OnceEvent);\r\n\r\n    function DataStore() {\r\n        this.addInput(\"data\", 0);\r\n        this.addInput(\"assign\", LiteGraph.ACTION);\r\n        this.addOutput(\"data\", 0);\r\n\t\tthis._last_value = null;\r\n\t\tthis.properties = { data: null, serialize: true };\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"store\",\"\",function(){\r\n\t\t\tthat.properties.data = that._last_value;\r\n\t\t});\r\n    }\r\n\r\n    DataStore.title = \"Data Store\";\r\n    DataStore.desc = \"Stores data and only changes when event is received\";\r\n\r\n\tDataStore.prototype.onExecute = function()\r\n\t{\r\n\t\tthis._last_value = this.getInputData(0);\r\n\t\tthis.setOutputData(0, this.properties.data );\r\n\t}\r\n\r\n    DataStore.prototype.onAction = function(action, param, options) {\r\n\t\tthis.properties.data = this._last_value;\r\n    };\r\n\r\n\tDataStore.prototype.onSerialize = function(o)\r\n\t{\r\n\t\tif(o.data == null)\r\n\t\t\treturn;\r\n\t\tif(this.properties.serialize == false || (o.data.constructor !== String && o.data.constructor !== Number && o.data.constructor !== Boolean && o.data.constructor !== Array && o.data.constructor !== Object ))\r\n\t\t\to.data = null;\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"basic/data_store\", DataStore);\r\n\r\n\r\n\r\n})(this);\r\n"
  },
  {
    "path": "src/nodes/geometry.js",
    "content": "(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n\tvar view_matrix = new Float32Array(16);\r\n\tvar projection_matrix = new Float32Array(16);\r\n\tvar viewprojection_matrix = new Float32Array(16);\r\n\tvar model_matrix = new Float32Array(16);\r\n\tvar global_uniforms = {\r\n\t\tu_view: view_matrix,\r\n\t\tu_projection: projection_matrix,\r\n\t\tu_viewprojection: viewprojection_matrix,\r\n\t\tu_model: model_matrix \r\n\t};\r\n\r\n\tLiteGraph.LGraphRender = {\r\n\t\tonRequestCameraMatrices: null //overwrite with your 3D engine specifics, it will receive (view_matrix, projection_matrix,viewprojection_matrix) and must be filled\r\n\t};\r\n\r\n\tfunction generateGeometryId() {\r\n\t\treturn (Math.random() * 100000)|0;\r\n\t}\r\n\r\n\tfunction LGraphPoints3D() {\r\n\r\n\t\tthis.addInput(\"obj\", \"\");\r\n\t\tthis.addInput(\"radius\", \"number\");\r\n\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\t\tthis.addOutput(\"points\", \"[vec3]\");\r\n\t\tthis.properties = {\r\n\t\t\tradius: 1,\r\n\t\t\tnum_points: 4096,\r\n\t\t\tgenerate_normals: true,\r\n\t\t\tregular: false,\r\n\t\t\tmode: LGraphPoints3D.SPHERE,\r\n\t\t\tforce_update: false\r\n\t\t};\r\n\r\n\t\tthis.points = new Float32Array( this.properties.num_points * 3 );\r\n\t\tthis.normals = new Float32Array( this.properties.num_points * 3 );\r\n\t\tthis.must_update = true;\r\n\t\tthis.version = 0;\r\n\r\n\t\tvar that = this;\r\n\t\tthis.addWidget(\"button\",\"update\",null, function(){ that.must_update = true; });\r\n\r\n\t\tthis.geometry = {\r\n\t\t\tvertices: null,\r\n\t\t\t_id: generateGeometryId()\r\n\t\t}\r\n\r\n\t\tthis._old_obj = null;\r\n\t\tthis._last_radius = null;\r\n\t}\r\n\r\n\tglobal.LGraphPoints3D = LGraphPoints3D;\r\n\r\n\tLGraphPoints3D.RECTANGLE = 1;\r\n\tLGraphPoints3D.CIRCLE = 2;\r\n\r\n\tLGraphPoints3D.CUBE = 10;\r\n\tLGraphPoints3D.SPHERE = 11;\r\n\tLGraphPoints3D.HEMISPHERE = 12;\r\n\tLGraphPoints3D.INSIDE_SPHERE = 13;\r\n\r\n\tLGraphPoints3D.OBJECT = 20;\r\n\tLGraphPoints3D.OBJECT_UNIFORMLY = 21;\r\n\tLGraphPoints3D.OBJECT_INSIDE = 22;\r\n\r\n\tLGraphPoints3D.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 };\r\n\r\n\tLGraphPoints3D.widgets_info = {\r\n\t\tmode: { widget: \"combo\", values: LGraphPoints3D.MODE_VALUES }\r\n\t};\r\n\r\n\tLGraphPoints3D.title = \"list of points\";\r\n\tLGraphPoints3D.desc = \"returns an array of points\";\r\n\r\n\tLGraphPoints3D.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tthis.must_update = true;\r\n\t}\r\n\r\n\tLGraphPoints3D.prototype.onExecute = function() {\r\n\r\n\t\tvar obj = this.getInputData(0);\r\n\t\tif( obj != this._old_obj || (obj && obj._version != this._old_obj_version) )\r\n\t\t{\r\n\t\t\tthis._old_obj = obj;\r\n\t\t\tthis.must_update = true;\r\n\t\t}\r\n\r\n\t\tvar radius = this.getInputData(1);\r\n\t\tif(radius == null)\r\n\t\t\tradius = this.properties.radius;\r\n\t\tif( this._last_radius != radius )\r\n\t\t{\r\n\t\t\tthis._last_radius = radius;\r\n\t\t\tthis.must_update = true;\r\n\t\t}\r\n\r\n\t\tif(this.must_update || this.properties.force_update )\r\n\t\t{\r\n\t\t\tthis.must_update = false;\r\n\t\t\tthis.updatePoints();\r\n\t\t}\r\n\r\n\t\tthis.geometry.vertices = this.points;\r\n\t\tthis.geometry.normals = this.normals;\r\n\t\tthis.geometry._version = this.version;\r\n\r\n\t\tthis.setOutputData( 0, this.geometry );\r\n\t}\r\n\r\n\tLGraphPoints3D.prototype.updatePoints = function() {\r\n\t\tvar num_points = this.properties.num_points|0;\r\n\t\tif(num_points < 1)\r\n\t\t\tnum_points = 1;\r\n\r\n\t\tif(!this.points || this.points.length != num_points * 3)\r\n\t\t\tthis.points = new Float32Array( num_points * 3 );\r\n\r\n\t\tif(this.properties.generate_normals)\r\n\t\t{\r\n\t\t\tif (!this.normals || this.normals.length != this.points.length)\r\n\t\t\t\tthis.normals = new Float32Array( this.points.length );\r\n\t\t}\r\n\t\telse\r\n\t\t\tthis.normals = null;\r\n\r\n\t\tvar radius = this._last_radius || this.properties.radius;\r\n\t\tvar mode = this.properties.mode;\r\n\r\n\t\tvar obj = this.getInputData(0);\r\n\t\tthis._old_obj_version = obj ? obj._version : null;\r\n\r\n\t\tthis.points = LGraphPoints3D.generatePoints( radius, num_points, mode, this.points, this.normals, this.properties.regular, obj );\r\n\r\n\t\tthis.version++;\r\n\t}\r\n\r\n\t//global\r\n\tLGraphPoints3D.generatePoints = function( radius, num_points, mode, points, normals, regular, obj )\r\n\t{\r\n\t\tvar size = num_points * 3;\r\n\t\tif(!points || points.length != size)\r\n\t\t\tpoints = new Float32Array( size );\r\n\t\tvar temp = new Float32Array(3);\r\n\t\tvar UP = new Float32Array([0,1,0]);\r\n\r\n\t\tif(regular)\r\n\t\t{\r\n\t\t\tif( mode == LGraphPoints3D.RECTANGLE)\r\n\t\t\t{\r\n\t\t\t\tvar side = Math.floor(Math.sqrt(num_points));\r\n\t\t\t\tfor(var i = 0; i < side; ++i)\r\n\t\t\t\tfor(var j = 0; j < side; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar pos = i*3 + j*3*side;\r\n\t\t\t\t\tpoints[pos] = ((i/side) - 0.5) * radius * 2;\r\n\t\t\t\t\tpoints[pos+1] = 0;\r\n\t\t\t\t\tpoints[pos+2] = ((j/side) - 0.5) * radius * 2;\r\n\t\t\t\t}\r\n\t\t\t\tpoints = new Float32Array( points.subarray(0,side*side*3) );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t\t\t\t\tnormals.set(UP, i);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.SPHERE)\r\n\t\t\t{\r\n\t\t\t\tvar side = Math.floor(Math.sqrt(num_points));\r\n\t\t\t\tfor(var i = 0; i < side; ++i)\r\n\t\t\t\tfor(var j = 0; j < side; ++j)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar pos = i*3 + j*3*side;\r\n\t\t\t\t\tpolarToCartesian( temp, (i/side) * 2 * Math.PI, ((j/side) - 0.5) * 2 * Math.PI, radius );\r\n\t\t\t\t\tpoints[pos] = temp[0];\r\n\t\t\t\t\tpoints[pos+1] = temp[1];\r\n\t\t\t\t\tpoints[pos+2] = temp[2];\r\n\t\t\t\t}\r\n\t\t\t\tpoints = new Float32Array( points.subarray(0,side*side*3) );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.CIRCLE)\r\n\t\t\t{\r\n\t\t\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar angle = 2 * Math.PI * (i/size);\r\n\t\t\t\t\tpoints[i] = Math.cos( angle ) * radius;\r\n\t\t\t\t\tpoints[i+1] = 0;\r\n\t\t\t\t\tpoints[i+2] = Math.sin( angle ) * radius;\r\n\t\t\t\t}\r\n\t\t\t\tif(normals)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t\t\t\t\tnormals.set(UP, i);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse //non regular\r\n\t\t{\r\n\t\t\tif( mode == LGraphPoints3D.RECTANGLE)\r\n\t\t\t{\r\n\t\t\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t\t\t{\r\n\t\t\t\t\tpoints[i] = (Math.random() - 0.5) * radius * 2;\r\n\t\t\t\t\tpoints[i+1] = 0;\r\n\t\t\t\t\tpoints[i+2] = (Math.random() - 0.5) * radius * 2;\r\n\t\t\t\t}\r\n\t\t\t\tif(normals)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t\t\t\t\tnormals.set(UP, i);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.CUBE)\r\n\t\t\t{\r\n\t\t\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t\t\t{\r\n\t\t\t\t\tpoints[i] = (Math.random() - 0.5) * radius * 2;\r\n\t\t\t\t\tpoints[i+1] = (Math.random() - 0.5) * radius * 2;\r\n\t\t\t\t\tpoints[i+2] = (Math.random() - 0.5) * radius * 2;\r\n\t\t\t\t}\r\n\t\t\t\tif(normals)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t\t\t\t\tnormals.set(UP, i);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.SPHERE)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateSphere( points, size, radius );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.HEMISPHERE)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateHemisphere( points, size, radius );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.CIRCLE)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateInsideCircle( points, size, radius );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.INSIDE_SPHERE)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateInsideSphere( points, size, radius );\r\n\t\t\t\tif(normals)\r\n\t\t\t\t\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.OBJECT)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateFromObject( points, normals, size, obj, false );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.OBJECT_UNIFORMLY)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateFromObject( points, normals, size, obj, true );\r\n\t\t\t}\r\n\t\t\telse if( mode == LGraphPoints3D.OBJECT_INSIDE)\r\n\t\t\t{\r\n\t\t\t\tLGraphPoints3D.generateFromInsideObject( points, size, obj );\r\n\t\t\t\t//if(normals)\r\n\t\t\t\t//\tLGraphPoints3D.generateSphericalNormals( points, normals );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tconsole.warn(\"wrong mode in LGraphPoints3D\");\r\n\t\t}\r\n\r\n\t\treturn points;\r\n\t}\r\n\r\n\tLGraphPoints3D.generateSphericalNormals = function(points, normals)\r\n\t{\r\n\t\tvar temp = new Float32Array(3);\r\n\t\tfor(var i = 0; i < normals.length; i+=3)\r\n\t\t{\r\n\t\t\ttemp[0] = points[i];\r\n\t\t\ttemp[1] = points[i+1];\r\n\t\t\ttemp[2] = points[i+2];\r\n\t\t\tvec3.normalize(temp,temp);\r\n\t\t\tnormals.set(temp,i);\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphPoints3D.generateSphere = function (points, size, radius)\r\n\t{\r\n\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t{\r\n\t\t\tvar r1 = Math.random();\r\n\t\t\tvar r2 = Math.random();\r\n\t\t\tvar x = 2 * Math.cos( 2 * Math.PI * r1 ) * Math.sqrt( r2 * (1-r2) );\r\n\t\t\tvar y = 1 - 2 * r2;\r\n\t\t\tvar z = 2 * Math.sin( 2 * Math.PI * r1 ) * Math.sqrt( r2 * (1-r2) );\r\n\t\t\tpoints[i] = x * radius;\r\n\t\t\tpoints[i+1] = y * radius;\r\n\t\t\tpoints[i+2] = z * radius;\r\n\t\t}\t\t\t\r\n\t}\r\n\r\n\tLGraphPoints3D.generateHemisphere = function (points, size, radius)\r\n\t{\r\n\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t{\r\n\t\t\tvar r1 = Math.random();\r\n\t\t\tvar r2 = Math.random();\r\n\t\t\tvar x = Math.cos( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );\r\n\t\t\tvar y = r2;\r\n\t\t\tvar z = Math.sin( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );\r\n\t\t\tpoints[i] = x * radius;\r\n\t\t\tpoints[i+1] = y * radius;\r\n\t\t\tpoints[i+2] = z * radius;\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphPoints3D.generateInsideCircle = function (points, size, radius)\r\n\t{\r\n\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t{\r\n\t\t\tvar r1 = Math.random();\r\n\t\t\tvar r2 = Math.random();\r\n\t\t\tvar x = Math.cos( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );\r\n\t\t\tvar y = r2;\r\n\t\t\tvar z = Math.sin( 2 * Math.PI * r1 ) * Math.sqrt(1 - r2*r2 );\r\n\t\t\tpoints[i] = x * radius;\r\n\t\t\tpoints[i+1] = 0;\r\n\t\t\tpoints[i+2] = z * radius;\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphPoints3D.generateInsideSphere = function (points, size, radius)\r\n\t{\r\n\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t{\r\n\t\t\tvar u = Math.random();\r\n\t\t\tvar v = Math.random();\r\n\t\t\tvar theta = u * 2.0 * Math.PI;\r\n\t\t\tvar phi = Math.acos(2.0 * v - 1.0);\r\n\t\t\tvar r = Math.cbrt(Math.random()) * radius;\r\n\t\t\tvar sinTheta = Math.sin(theta);\r\n\t\t\tvar cosTheta = Math.cos(theta);\r\n\t\t\tvar sinPhi = Math.sin(phi);\r\n\t\t\tvar cosPhi = Math.cos(phi);\r\n\t\t\tpoints[i] = r * sinPhi * cosTheta;\r\n\t\t\tpoints[i+1] = r * sinPhi * sinTheta;\r\n\t\t\tpoints[i+2] = r * cosPhi;\r\n\t\t}\t\r\n\t}\r\n\r\n\tfunction findRandomTriangle( areas, f )\r\n\t{\r\n\t\tvar l = areas.length;\r\n\t\tvar imin = 0;\r\n\t\tvar imid = 0;\r\n\t\tvar imax = l;\r\n\r\n\t\tif(l == 0)\r\n\t\t\treturn -1;\r\n\t\tif(l == 1)\r\n\t\t\treturn 0;\r\n\t\t//dichotomic search\r\n\t\twhile (imax >= imin)\r\n\t\t{\r\n\t\t\timid = ((imax + imin)*0.5)|0;\r\n\t\t\tvar t = areas[ imid ];\r\n\t\t\tif( t == f )\r\n\t\t\t\treturn imid; \r\n\t\t\tif( imin == (imax - 1) )\r\n\t\t\t\treturn imin;\r\n\t\t\tif (t < f)\r\n\t\t\t\timin = imid;\r\n\t\t\telse         \r\n\t\t\t\timax = imid;\r\n\t\t}\r\n\t\treturn imid;\t\t\r\n\t}\r\n\r\n\tLGraphPoints3D.generateFromObject = function( points, normals, size, obj, evenly )\r\n\t{\r\n\t\tif(!obj)\r\n\t\t\treturn;\r\n\r\n\t\tvar vertices = null;\r\n\t\tvar mesh_normals = null;\r\n\t\tvar indices = null;\r\n\t\tvar areas = null;\r\n\t\tif( obj.constructor === GL.Mesh )\r\n\t\t{\r\n\t\t\tvertices = obj.vertexBuffers.vertices.data;\r\n\t\t\tmesh_normals = obj.vertexBuffers.normals ? obj.vertexBuffers.normals.data : null;\r\n\t\t\tindices = obj.indexBuffers.indices ? obj.indexBuffers.indices.data : null;\r\n\t\t\tif(!indices)\r\n\t\t\t\tindices = obj.indexBuffers.triangles ? obj.indexBuffers.triangles.data : null;\r\n\t\t}\r\n\t\tif(!vertices)\r\n\t\t\treturn null;\r\n\t\tvar num_triangles = indices ? indices.length / 3 : vertices.length / (3*3);\r\n\t\tvar total_area = 0; //sum of areas of all triangles\r\n\r\n\t\tif(evenly)\r\n\t\t{\r\n\t\t\tareas = new Float32Array(num_triangles); //accum\r\n\t\t\tfor(var i = 0; i < num_triangles; ++i)\r\n\t\t\t{\r\n\t\t\t\tif(indices)\r\n\t\t\t\t{\r\n\t\t\t\t\ta = indices[i*3]*3;\r\n\t\t\t\t\tb = indices[i*3+1]*3;\r\n\t\t\t\t\tc = indices[i*3+2]*3;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\ta = i*9;\r\n\t\t\t\t\tb = i*9+3;\r\n\t\t\t\t\tc = i*9+6;\r\n\t\t\t\t}\r\n\t\t\t\tvar P1 = vertices.subarray(a,a+3);\r\n\t\t\t\tvar P2 = vertices.subarray(b,b+3);\r\n\t\t\t\tvar P3 = vertices.subarray(c,c+3);\r\n\t\t\t\tvar aL = vec3.distance( P1, P2 );\r\n\t\t\t\tvar bL = vec3.distance( P2, P3 );\r\n\t\t\t\tvar cL = vec3.distance( P3, P1 );\r\n\t\t\t\tvar s = (aL + bL+ cL) / 2;\r\n\t\t\t\ttotal_area += Math.sqrt(s * (s - aL) * (s - bL) * (s - cL));\r\n\t\t\t\tareas[i] = total_area;\r\n\t\t\t}\t\t\t\r\n\t\t\tfor(var i = 0; i < num_triangles; ++i) //normalize\r\n\t\t\t\tareas[i] /= total_area;\r\n\t\t}\r\n\r\n\t\tfor(var i = 0; i < size; i+=3)\r\n\t\t{\r\n\t\t\tvar r = Math.random();\r\n\t\t\tvar index = evenly ? findRandomTriangle( areas, r ) : Math.floor(r * num_triangles );\r\n\t\t\t//get random triangle\r\n\t\t\tvar a = 0;\r\n\t\t\tvar b = 0;\r\n\t\t\tvar c = 0;\r\n\t\t\tif(indices)\r\n\t\t\t{\r\n\t\t\t\ta = indices[index*3]*3;\r\n\t\t\t\tb = indices[index*3+1]*3;\r\n\t\t\t\tc = indices[index*3+2]*3;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\ta = index*9;\r\n\t\t\t\tb = index*9+3;\r\n\t\t\t\tc = index*9+6;\r\n\t\t\t}\r\n\t\t\tvar s = Math.random();\r\n\t\t\tvar t = Math.random();\r\n\t\t\tvar sqrt_s = Math.sqrt(s);\r\n\t\t\tvar af = 1 - sqrt_s;\r\n\t\t\tvar bf = sqrt_s * ( 1 - t);\r\n\t\t\tvar cf = t * sqrt_s;\r\n\t\t\tpoints[i] = af * vertices[a] + bf*vertices[b] + cf*vertices[c];\r\n\t\t\tpoints[i+1] = af * vertices[a+1] + bf*vertices[b+1] + cf*vertices[c+1];\r\n\t\t\tpoints[i+2] = af * vertices[a+2] + bf*vertices[b+2] + cf*vertices[c+2];\r\n\t\t\tif(normals && mesh_normals)\r\n\t\t\t{\r\n\t\t\t\tnormals[i] = af * mesh_normals[a] + bf*mesh_normals[b] + cf*mesh_normals[c];\r\n\t\t\t\tnormals[i+1] = af * mesh_normals[a+1] + bf*mesh_normals[b+1] + cf*mesh_normals[c+1];\r\n\t\t\t\tnormals[i+2] = af * mesh_normals[a+2] + bf*mesh_normals[b+2] + cf*mesh_normals[c+2];\r\n\t\t\t\tvar N = normals.subarray(i,i+3);\r\n\t\t\t\tvec3.normalize(N,N);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphPoints3D.generateFromInsideObject = function( points, size, mesh )\r\n\t{\r\n\t\tif(!mesh || mesh.constructor !== GL.Mesh)\r\n\t\t\treturn;\r\n\r\n\t\tvar aabb = mesh.getBoundingBox();\r\n\t\tif(!mesh.octree)\r\n\t\t\tmesh.octree = new GL.Octree( mesh );\r\n\t\tvar octree = mesh.octree;\r\n\t\tvar origin = vec3.create();\r\n\t\tvar direction = vec3.fromValues(1,0,0);\r\n\t\tvar temp = vec3.create();\r\n\t\tvar i = 0;\r\n\t\tvar tries = 0;\r\n\t\twhile(i < size && tries < points.length * 10) //limit to avoid problems\r\n\t\t{\r\n\t\t\ttries += 1\r\n\t\t\tvar r = vec3.random(temp); //random point inside the aabb\r\n\t\t\tr[0] = (r[0] * 2 - 1) * aabb[3] + aabb[0];\r\n\t\t\tr[1] = (r[1] * 2 - 1) * aabb[4] + aabb[1];\r\n\t\t\tr[2] = (r[2] * 2 - 1) * aabb[5] + aabb[2];\r\n\t\t\torigin.set(r);\r\n\t\t\tvar hit = octree.testRay( origin, direction, 0, 10000, true, GL.Octree.ALL );\r\n\t\t\tif(!hit || hit.length % 2 == 0) //not inside\r\n\t\t\t\tcontinue;\r\n\t\t\tpoints.set( r, i );\r\n\t\t\ti+=3;\r\n\t\t}\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/points3D\", LGraphPoints3D );\r\n\r\n\r\n\r\n\tfunction LGraphPointsToInstances() {\r\n\t\tthis.addInput(\"points\", \"geometry\");\r\n\t\tthis.addOutput(\"instances\", \"[mat4]\");\r\n\t\tthis.properties = {\r\n\t\t\tmode: 1,\r\n\t\t\tautoupdate: true\r\n\t\t};\r\n\r\n\t\tthis.must_update = true;\r\n\t\tthis.matrices = [];\r\n\t\tthis.first_time = true;\r\n\t}\r\n\r\n\tLGraphPointsToInstances.NORMAL = 0;\r\n\tLGraphPointsToInstances.VERTICAL = 1;\r\n\tLGraphPointsToInstances.SPHERICAL = 2;\r\n\tLGraphPointsToInstances.RANDOM = 3;\r\n\tLGraphPointsToInstances.RANDOM_VERTICAL = 4;\r\n\r\n\tLGraphPointsToInstances.modes = {\"normal\":0,\"vertical\":1,\"spherical\":2,\"random\":3,\"random_vertical\":4};\r\n\tLGraphPointsToInstances.widgets_info = {\r\n\t\tmode: { widget: \"combo\", values: LGraphPointsToInstances.modes }\r\n\t};\r\n\r\n\tLGraphPointsToInstances.title = \"points to inst\";\r\n\r\n\tLGraphPointsToInstances.prototype.onExecute = function()\r\n\t{\r\n\t\tvar geo = this.getInputData(0);\r\n\t\tif( !geo )\r\n\t\t{\r\n\t\t\tthis.setOutputData(0,null);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif( !this.isOutputConnected(0) )\r\n\t\t\treturn;\r\n\r\n\t\tvar has_changed = (geo._version != this._version || geo._id != this._geometry_id);\r\n\r\n\t\tif( has_changed && this.properties.autoupdate || this.first_time )\r\n\t\t{\r\n\t\t\tthis.first_time = false;\r\n\t\t\tthis.updateInstances( geo );\r\n\t\t}\r\n\r\n\t\tthis.setOutputData( 0, this.matrices );\r\n\t}\r\n\r\n\tLGraphPointsToInstances.prototype.updateInstances = function( geometry )\r\n\t{\r\n\t\tvar vertices = geometry.vertices;\r\n\t\tif(!vertices)\r\n\t\t\treturn null;\r\n\t\tvar normals = geometry.normals;\r\n\r\n\t\tvar matrices = this.matrices;\r\n\t\tvar num_points = vertices.length / 3;\r\n\t\tif( matrices.length != num_points)\r\n\t\t\tmatrices.length = num_points;\r\n\t\tvar identity = mat4.create();\r\n\t\tvar temp = vec3.create();\r\n\t\tvar zero = vec3.create();\r\n\t\tvar UP = vec3.fromValues(0,1,0);\r\n\t\tvar FRONT = vec3.fromValues(0,0,-1);\r\n\t\tvar RIGHT = vec3.fromValues(1,0,0);\r\n\t\tvar R = quat.create();\r\n\r\n\t\tvar front = vec3.create();\r\n\t\tvar right = vec3.create();\r\n\t\tvar top = vec3.create();\r\n\r\n\t\tfor(var i = 0; i < vertices.length; i += 3)\r\n\t\t{\r\n\t\t\tvar index = i/3;\r\n\t\t\tvar m = matrices[index];\r\n\t\t\tif(!m)\r\n\t\t\t\tm = matrices[index] = mat4.create();\r\n\t\t\tm.set( identity );\r\n\t\t\tvar point = vertices.subarray(i,i+3);\r\n\r\n\t\t\tswitch(this.properties.mode)\r\n\t\t\t{\r\n\t\t\t\tcase LGraphPointsToInstances.NORMAL: \r\n\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\tif(normals)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar normal = normals.subarray(i,i+3);\r\n\t\t\t\t\t\ttop.set( normal );\r\n\t\t\t\t\t\tvec3.normalize( top, top );\r\n\t\t\t\t\t\tvec3.cross( right, FRONT, top );\r\n\t\t\t\t\t\tvec3.normalize( right, right );\r\n\t\t\t\t\t\tvec3.cross( front, right, top );\r\n\t\t\t\t\t\tvec3.normalize( front, front );\r\n\t\t\t\t\t\tm.set(right,0);\r\n\t\t\t\t\t\tm.set(top,4);\r\n\t\t\t\t\t\tm.set(front,8);\r\n\t\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase LGraphPointsToInstances.VERTICAL: \r\n\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase LGraphPointsToInstances.SPHERICAL: \r\n\t\t\t\t\tfront.set( point );\r\n\t\t\t\t\tvec3.normalize( front, front );\r\n\t\t\t\t\tvec3.cross( right, UP, front );\r\n\t\t\t\t\tvec3.normalize( right, right );\r\n\t\t\t\t\tvec3.cross( top, front, right );\r\n\t\t\t\t\tvec3.normalize( top, top );\r\n\t\t\t\t\tm.set(right,0);\r\n\t\t\t\t\tm.set(top,4);\r\n\t\t\t\t\tm.set(front,8);\r\n\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase LGraphPointsToInstances.RANDOM:\r\n\t\t\t\t\ttemp[0] = Math.random()*2 - 1;\r\n\t\t\t\t\ttemp[1] = Math.random()*2 - 1;\r\n\t\t\t\t\ttemp[2] = Math.random()*2 - 1;\r\n\t\t\t\t\tvec3.normalize( temp, temp );\r\n\t\t\t\t\tquat.setAxisAngle( R, temp, Math.random() * 2 * Math.PI );\r\n\t\t\t\t\tmat4.fromQuat(m, R);\r\n\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase LGraphPointsToInstances.RANDOM_VERTICAL:\r\n\t\t\t\t\tquat.setAxisAngle( R, UP, Math.random() * 2 * Math.PI );\r\n\t\t\t\t\tmat4.fromQuat(m, R);\r\n\t\t\t\t\tmat4.setTranslation( m, point );\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis._version = geometry._version;\r\n\t\tthis._geometry_id = geometry._id;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/points_to_instances\", LGraphPointsToInstances );\r\n\r\n\r\n\tfunction LGraphGeometryTransform() {\r\n\t\tthis.addInput(\"in\", \"geometry,[mat4]\");\r\n\t\tthis.addInput(\"mat4\", \"mat4\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\t\tthis.properties = {};\r\n\r\n\t\tthis.geometry = {\r\n\t\t\ttype: \"triangles\",\r\n\t\t\tvertices: null,\r\n\t\t\t_id: generateGeometryId(),\r\n\t\t\t_version: 0\r\n\t\t};\r\n\r\n\t\tthis._last_geometry_id = -1;\r\n\t\tthis._last_version = -1;\r\n\t\tthis._last_key = \"\";\r\n\r\n\t\tthis.must_update = true;\r\n\t}\r\n\r\n\tLGraphGeometryTransform.title = \"Transform\";\r\n\r\n\tLGraphGeometryTransform.prototype.onExecute = function() {\r\n\r\n\t\tvar input = this.getInputData(0);\r\n\t\tvar model = this.getInputData(1);\r\n\r\n\t\tif(!input)\r\n\t\t\treturn;\r\n\r\n\t\t//array of matrices\r\n\t\tif(input.constructor === Array)\r\n\t\t{\r\n\t\t\tif(input.length == 0)\r\n\t\t\t\treturn;\r\n\t\t\tthis.outputs[0].type = \"[mat4]\";\r\n\t\t\tif( !this.isOutputConnected(0) )\r\n\t\t\t\treturn;\r\n\r\n\t\t\tif(!model)\r\n\t\t\t{\r\n\t\t\t\tthis.setOutputData(0,input);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif(!this._output)\r\n\t\t\t\tthis._output = new Array();\r\n\t\t\tif(this._output.length != input.length)\r\n\t\t\t\tthis._output.length = input.length;\r\n\t\t\tfor(var i = 0; i < input.length; ++i)\r\n\t\t\t{\r\n\t\t\t\tvar m = this._output[i];\r\n\t\t\t\tif(!m)\r\n\t\t\t\t\tm = this._output[i] = mat4.create();\r\n\t\t\t\tmat4.multiply(m,input[i],model);\r\n\t\t\t}\r\n\t\t\tthis.setOutputData(0,this._output);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t//geometry\r\n\t\tif(!input.vertices || !input.vertices.length)\r\n\t\t\treturn;\r\n\t\tvar geo = input;\r\n\t\tthis.outputs[0].type = \"geometry\";\r\n\t\tif( !this.isOutputConnected(0) )\r\n\t\t\treturn;\r\n\t\tif(!model)\r\n\t\t{\r\n\t\t\tthis.setOutputData(0,geo);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar key = typedArrayToArray(model).join(\",\");\r\n\r\n\t\tif( this.must_update || geo._id != this._last_geometry_id || geo._version != this._last_version || key != this._last_key )\r\n\t\t{\r\n\t\t\tthis.updateGeometry(geo, model);\r\n\t\t\tthis._last_key = key;\r\n\t\t\tthis._last_version = geo._version;\r\n\t\t\tthis._last_geometry_id = geo._id;\r\n\t\t\tthis.must_update = false;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0,this.geometry);\r\n\t}\r\n\r\n\tLGraphGeometryTransform.prototype.updateGeometry = function(geometry, model) {\r\n\t\tvar old_vertices = geometry.vertices;\r\n\t\tvar vertices = this.geometry.vertices;\r\n\t\tif( !vertices || vertices.length != old_vertices.length )\r\n\t\t\tvertices = this.geometry.vertices = new Float32Array( old_vertices.length );\r\n\t\tvar temp = vec3.create();\r\n\r\n\t\tfor(var i = 0, l = vertices.length; i < l; i+=3)\r\n\t\t{\r\n\t\t\ttemp[0] = old_vertices[i]; temp[1] = old_vertices[i+1]; temp[2] = old_vertices[i+2]; \r\n\t\t\tmat4.multiplyVec3( temp, model, temp );\r\n\t\t\tvertices[i] = temp[0]; vertices[i+1] = temp[1]; vertices[i+2] = temp[2];\r\n\t\t}\r\n\r\n\t\tif(geometry.normals)\r\n\t\t{\r\n\t\t\tif( !this.geometry.normals || this.geometry.normals.length != geometry.normals.length )\r\n\t\t\t\tthis.geometry.normals = new Float32Array( geometry.normals.length );\r\n\t\t\tvar normals = this.geometry.normals;\r\n\t\t\tvar normal_model = mat4.invert(mat4.create(), model);\r\n\t\t\tif(normal_model)\r\n\t\t\t\tmat4.transpose(normal_model, normal_model);\r\n\t\t\tvar old_normals = geometry.normals;\r\n\t\t\tfor(var i = 0, l = normals.length; i < l; i+=3)\r\n\t\t\t{\r\n\t\t\t\ttemp[0] = old_normals[i]; temp[1] = old_normals[i+1]; temp[2] = old_normals[i+2]; \r\n\t\t\t\tmat4.multiplyVec3( temp, normal_model, temp );\r\n\t\t\t\tnormals[i] = temp[0]; normals[i+1] = temp[1]; normals[i+2] = temp[2];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.geometry.type = geometry.type;\r\n\t\tthis.geometry._version++;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/transform\", LGraphGeometryTransform );\r\n\r\n\r\n\tfunction LGraphGeometryPolygon() {\r\n\t\tthis.addInput(\"sides\", \"number\");\r\n\t\tthis.addInput(\"radius\", \"number\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\t\tthis.properties = { sides: 6, radius: 1, uvs: false }\r\n\r\n\t\tthis.geometry = {\r\n\t\t\ttype: \"line_loop\",\r\n\t\t\tvertices: null,\r\n\t\t\t_id: generateGeometryId()\r\n\t\t};\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.must_update = true;\r\n\r\n\t\tthis.last_info = { sides: -1, radius: -1 };\r\n\t}\r\n\r\n\tLGraphGeometryPolygon.title = \"Polygon\";\r\n\r\n\tLGraphGeometryPolygon.prototype.onExecute = function() {\r\n\r\n\t\tif( !this.isOutputConnected(0) )\r\n\t\t\treturn;\r\n\r\n\t\tvar sides = this.getInputOrProperty(\"sides\");\r\n\t\tvar radius = this.getInputOrProperty(\"radius\");\r\n\t\tsides = Math.max(3,sides)|0;\r\n\r\n\t\t//update\r\n\t\tif( this.last_info.sides != sides || this.last_info.radius != radius )\r\n\t\t\tthis.updateGeometry(sides, radius);\r\n\r\n\t\tthis.setOutputData(0,this.geometry);\r\n\t}\r\n\r\n\tLGraphGeometryPolygon.prototype.updateGeometry = function(sides, radius) {\r\n\t\tvar num = 3*sides;\r\n\t\tvar vertices = this.geometry.vertices;\r\n\t\tif( !vertices || vertices.length != num )\r\n\t\t\tvertices = this.geometry.vertices = new Float32Array( 3*sides );\r\n\t\tvar delta = (Math.PI * 2) / sides;\r\n\t\tvar gen_uvs = this.properties.uvs;\r\n\t\tif(gen_uvs)\r\n\t\t{\r\n\t\t\tuvs = this.geometry.coords = new Float32Array( 3*sides );\r\n\t\t}\r\n\r\n\r\n\t\tfor(var i = 0; i < sides; ++i)\r\n\t\t{\r\n\t\t\tvar angle = delta * -i;\r\n\t\t\tvar x = Math.cos( angle ) * radius;\r\n\t\t\tvar y = 0;\r\n\t\t\tvar z = Math.sin( angle ) * radius;\r\n\t\t\tvertices[i*3] = x;\r\n\t\t\tvertices[i*3+1] = y;\r\n\t\t\tvertices[i*3+2] = z;\r\n\r\n\t\t\tif(gen_uvs)\r\n\t\t\t{\r\n\t\t\t\t\r\n\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis.geometry._id = ++this.geometry_id;\r\n\t\tthis.geometry._version = ++this.version;\r\n\t\tthis.last_info.sides = sides;\r\n\t\tthis.last_info.radius = radius;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/polygon\", LGraphGeometryPolygon );\r\n\r\n\r\n\tfunction LGraphGeometryExtrude() {\r\n\r\n\t\tthis.addInput(\"\", \"geometry\");\r\n\t\tthis.addOutput(\"\", \"geometry\");\r\n\t\tthis.properties = { top_cap: true, bottom_cap: true, offset: [0,100,0] };\r\n\t\tthis.version = -1;\r\n\r\n\t\tthis._last_geo_version = -1;\r\n\t\tthis._must_update = true;\r\n\t}\r\n\r\n\tLGraphGeometryExtrude.title = \"extrude\";\r\n\r\n\tLGraphGeometryExtrude.prototype.onPropertyChanged = function(name, value)\r\n\t{\r\n\t\tthis._must_update = true;\r\n\t}\r\n\r\n\tLGraphGeometryExtrude.prototype.onExecute = function()\r\n\t{\r\n\t\tvar geo = this.getInputData(0);\r\n\t\tif( !geo || !this.isOutputConnected(0) )\r\n\t\t\treturn;\r\n\r\n\t\tif(geo.version != this._last_geo_version || this._must_update)\r\n\t\t{\r\n\t\t\tthis._geo = this.extrudeGeometry( geo, this._geo );\r\n\t\t\tif(this._geo)\r\n\t\t\t\tthis._geo.version = this.version++;\r\n\t\t\tthis._must_update = false;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0, this._geo);\r\n\t}\r\n\r\n\tLGraphGeometryExtrude.prototype.extrudeGeometry = function( geo )\r\n\t{\r\n\t\t//for every pair of vertices\r\n\t\tvar vertices = geo.vertices;\r\n\t\tvar num_points = vertices.length / 3;\r\n\r\n\t\tvar tempA = vec3.create();\r\n\t\tvar tempB = vec3.create();\r\n\t\tvar tempC = vec3.create();\r\n\t\tvar tempD = vec3.create();\r\n\t\tvar offset = new Float32Array( this.properties.offset );\r\n\r\n\t\tif(geo.type == \"line_loop\")\r\n\t\t{\r\n\t\t\tvar new_vertices = new Float32Array( num_points * 6 * 3 ); //every points become 6 ( caps not included )\r\n\t\t\tvar npos = 0;\r\n\t\t\tfor(var i = 0, l = vertices.length; i < l; i += 3)\r\n\t\t\t{\r\n\t\t\t\ttempA[0] = vertices[i]; tempA[1] = vertices[i+1]; tempA[2] = vertices[i+2];\r\n\r\n\t\t\t\tif( i+3 < l ) //loop\r\n\t\t\t\t{\r\n\t\t\t\t\ttempB[0] = vertices[i+3]; tempB[1] = vertices[i+4]; tempB[2] = vertices[i+5];\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\ttempB[0] = vertices[0]; tempB[1] = vertices[1]; tempB[2] = vertices[2];\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvec3.add( tempC, tempA, offset );\r\n\t\t\t\tvec3.add( tempD, tempB, offset );\r\n\r\n\t\t\t\tnew_vertices.set( tempA, npos ); npos += 3;\r\n\t\t\t\tnew_vertices.set( tempB, npos ); npos += 3;\r\n\t\t\t\tnew_vertices.set( tempC, npos ); npos += 3;\r\n\r\n\t\t\t\tnew_vertices.set( tempB, npos ); npos += 3;\r\n\t\t\t\tnew_vertices.set( tempD, npos ); npos += 3;\r\n\t\t\t\tnew_vertices.set( tempC, npos ); npos += 3;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar out_geo = {\r\n\t\t\t_id: generateGeometryId(),\r\n\t\t\ttype: \"triangles\",\r\n\t\t\tvertices: new_vertices\r\n\t\t};\r\n\r\n\t\treturn out_geo;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/extrude\", LGraphGeometryExtrude );\r\n\r\n\r\n\tfunction LGraphGeometryEval() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\r\n\t\tthis.properties = {\r\n\t\t\tcode: \"V[1] += 0.01 * Math.sin(I + T*0.001);\",\r\n\t\t\texecute_every_frame: false\r\n\t\t};\r\n\r\n\t\tthis.geometry = null;\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.must_update = true;\r\n\r\n\t\tthis.vertices = null;\r\n\t\tthis.func = null;\r\n\t}\r\n\r\n\tLGraphGeometryEval.title = \"geoeval\";\r\n\tLGraphGeometryEval.desc = \"eval code\";\r\n\r\n\tLGraphGeometryEval.widgets_info = {\r\n\t\tcode: { widget: \"code\" }\r\n\t};\r\n\r\n\tLGraphGeometryEval.prototype.onConfigure = function(o)\r\n\t{\r\n\t\tthis.compileCode();\r\n\t}\r\n\r\n\tLGraphGeometryEval.prototype.compileCode = function()\r\n\t{\r\n\t\tif(!this.properties.code)\r\n\t\t\treturn;\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tthis.func = new Function(\"V\",\"I\",\"T\", this.properties.code); \r\n\t\t\tthis.boxcolor = \"#AFA\";\r\n\t\t\tthis.must_update = true;\r\n\t\t}\r\n\t\tcatch (err)\r\n\t\t{\r\n\t\t\tthis.boxcolor = \"red\";\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphGeometryEval.prototype.onPropertyChanged = function(name, value)\r\n\t{\r\n\t\tif(name == \"code\")\r\n\t\t{\r\n\t\t\tthis.properties.code = value;\r\n\t\t\tthis.compileCode();\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphGeometryEval.prototype.onExecute = function() {\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\r\n\t\tif(!this.func)\r\n\t\t{\r\n\t\t\tthis.setOutputData(0,geometry);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif( this.geometry_id != geometry._id || this.version != geometry._version || this.must_update || this.properties.execute_every_frame )\r\n\t\t{\r\n\t\t\tthis.must_update = false;\r\n\t\t\tthis.geometry_id = geometry._id;\r\n\t\t\tif(this.properties.execute_every_frame)\r\n\t\t\t\tthis.version++;\r\n\t\t\telse\r\n\t\t\t\tthis.version = geometry._version;\r\n\t\t\tvar func = this.func;\r\n\t\t\tvar T = getTime();\r\n\r\n\t\t\t//clone\r\n\t\t\tif(!this.geometry)\r\n\t\t\t\tthis.geometry = {};\r\n\t\t\tfor(var i in geometry)\r\n\t\t\t{\r\n\t\t\t\tif(geometry[i] == null)\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\tif( geometry[i].constructor == Float32Array )\r\n\t\t\t\t\tthis.geometry[i] = new Float32Array( geometry[i] );\r\n\t\t\t\telse\r\n\t\t\t\t\tthis.geometry[i] = geometry[i];\r\n\t\t\t}\r\n\t\t\tthis.geometry._id = geometry._id;\r\n\t\t\tif(this.properties.execute_every_frame)\r\n\t\t\t\tthis.geometry._version = this.version;\r\n\t\t\telse\r\n\t\t\t\tthis.geometry._version = geometry._version + 1;\r\n\r\n\t\t\tvar V = vec3.create();\r\n\t\t\tvar vertices = this.vertices;\r\n\t\t\tif(!vertices || this.vertices.length != geometry.vertices.length)\r\n\t\t\t\tvertices = this.vertices = new Float32Array( geometry.vertices );\r\n\t\t\telse\r\n\t\t\t\tvertices.set( geometry.vertices );\r\n\t\t\tfor(var i = 0; i < vertices.length; i+=3)\r\n\t\t\t{\r\n\t\t\t\tV[0] = vertices[i];\r\n\t\t\t\tV[1] = vertices[i+1];\r\n\t\t\t\tV[2] = vertices[i+2];\r\n\t\t\t\tfunc(V,i/3,T);\r\n\t\t\t\tvertices[i] = V[0];\r\n\t\t\t\tvertices[i+1] = V[1];\r\n\t\t\t\tvertices[i+2] = V[2];\r\n\t\t\t}\r\n\t\t\tthis.geometry.vertices = vertices;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0,this.geometry);\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/eval\", LGraphGeometryEval );\r\n\r\n/*\r\nfunction LGraphGeometryDisplace() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addInput(\"img\", \"image\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\r\n\t\tthis.properties = {\r\n\t\t\tgrid_size: 1\r\n\t\t};\r\n\r\n\t\tthis.geometry = null;\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.must_update = true;\r\n\r\n\t\tthis.vertices = null;\r\n\t}\r\n\r\n\tLGraphGeometryDisplace.title = \"displace\";\r\n\tLGraphGeometryDisplace.desc = \"displace points\";\r\n\r\n\tLGraphGeometryDisplace.prototype.onExecute = function() {\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tvar image = this.getInputData(1);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\r\n\t\tif(!image)\r\n\t\t{\r\n\t\t\tthis.setOutputData(0,geometry);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif( this.geometry_id != geometry._id || this.version != geometry._version || this.must_update )\r\n\t\t{\r\n\t\t\tthis.must_update = false;\r\n\t\t\tthis.geometry_id = geometry._id;\r\n\t\t\tthis.version = geometry._version;\r\n\r\n\t\t\t//copy\r\n\t\t\tthis.geometry = {};\r\n\t\t\tfor(var i in geometry)\r\n\t\t\t\tthis.geometry[i] = geometry[i];\r\n\t\t\tthis.geometry._id = geometry._id;\r\n\t\t\tthis.geometry._version = geometry._version + 1;\r\n\r\n\t\t\tvar grid_size = this.properties.grid_size;\r\n\t\t\tif(grid_size != 0)\r\n\t\t\t{\r\n\t\t\t\tvar vertices = this.vertices;\r\n\t\t\t\tif(!vertices || this.vertices.length != this.geometry.vertices.length)\r\n\t\t\t\t\tvertices = this.vertices = new Float32Array( this.geometry.vertices );\r\n\t\t\t\tfor(var i = 0; i < vertices.length; i+=3)\r\n\t\t\t\t{\r\n\t\t\t\t\tvertices[i] = Math.round(vertices[i]/grid_size) * grid_size;\r\n\t\t\t\t\tvertices[i+1] = Math.round(vertices[i+1]/grid_size) * grid_size;\r\n\t\t\t\t\tvertices[i+2] = Math.round(vertices[i+2]/grid_size) * grid_size;\r\n\t\t\t\t}\r\n\t\t\t\tthis.geometry.vertices = vertices;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0,this.geometry);\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/displace\", LGraphGeometryDisplace );\r\n*/\r\n\r\n\tfunction LGraphConnectPoints() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\r\n\t\tthis.properties = {\r\n\t\t\tmin_dist: 0.4,\r\n\t\t\tmax_dist: 0.5,\r\n\t\t\tmax_connections: 0,\r\n\t\t\tprobability: 1\r\n\t\t};\r\n\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.my_version = 1;\r\n\t\tthis.must_update = true;\r\n\t}\r\n\r\n\tLGraphConnectPoints.title = \"connect points\";\r\n\tLGraphConnectPoints.desc = \"adds indices between near points\";\r\n\r\n\tLGraphConnectPoints.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tthis.must_update = true;\r\n\t}\r\n\r\n\tLGraphConnectPoints.prototype.onExecute = function() {\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\r\n\t\tif( this.geometry_id != geometry._id || this.version != geometry._version || this.must_update )\r\n\t\t{\r\n\t\t\tthis.must_update = false;\r\n\t\t\tthis.geometry_id = geometry._id;\r\n\t\t\tthis.version = geometry._version;\r\n\r\n\t\t\t//copy\r\n\t\t\tthis.geometry = {};\r\n\t\t\tfor(var i in geometry)\r\n\t\t\t\tthis.geometry[i] = geometry[i];\r\n\t\t\tthis.geometry._id = generateGeometryId();\r\n\t\t\tthis.geometry._version = this.my_version++;\r\n\r\n\t\t\tvar vertices = geometry.vertices;\r\n\t\t\tvar l = vertices.length;\r\n\t\t\tvar min_dist = this.properties.min_dist;\r\n\t\t\tvar max_dist = this.properties.max_dist;\r\n\t\t\tvar probability = this.properties.probability;\r\n\t\t\tvar max_connections = this.properties.max_connections;\r\n\t\t\tvar indices = [];\r\n\t\t\t\r\n\t\t\tfor(var i = 0; i < l; i+=3)\r\n\t\t\t{\r\n\t\t\t\tvar x = vertices[i];\r\n\t\t\t\tvar y = vertices[i+1];\r\n\t\t\t\tvar z = vertices[i+2];\r\n\t\t\t\tvar connections = 0;\r\n\t\t\t\tfor(var j = i+3; j < l; j+=3)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar x2 = vertices[j];\r\n\t\t\t\t\tvar y2 = vertices[j+1];\r\n\t\t\t\t\tvar z2 = vertices[j+2];\r\n\t\t\t\t\tvar dist = Math.sqrt( (x-x2)*(x-x2) + (y-y2)*(y-y2) + (z-z2)*(z-z2));\r\n\t\t\t\t\tif(dist > max_dist || dist < min_dist || (probability < 1 && probability < Math.random()) )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tindices.push(i/3,j/3);\r\n\t\t\t\t\tconnections += 1;\r\n\t\t\t\t\tif(max_connections && connections > max_connections)\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tthis.geometry.indices = this.indices = new Uint32Array(indices);\r\n\t\t}\r\n\r\n\t\tif(this.indices && this.indices.length)\r\n\t\t{\r\n\t\t\tthis.geometry.indices = this.indices;\r\n\t\t\tthis.setOutputData( 0, this.geometry );\r\n\t\t}\r\n\t\telse\r\n\t\t\tthis.setOutputData( 0, null );\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/connectPoints\", LGraphConnectPoints );\r\n\r\n    //Works with Litegl.js to create WebGL nodes\r\n    if (typeof GL == \"undefined\") //LiteGL RELATED **********************************************\r\n\t\treturn;\r\n\r\n\tfunction LGraphToGeometry() {\r\n\t\tthis.addInput(\"mesh\", \"mesh\");\r\n\t\tthis.addOutput(\"out\", \"geometry\");\r\n\r\n\t\tthis.geometry = {};\r\n\t\tthis.last_mesh = null;\r\n\t}\r\n\r\n\tLGraphToGeometry.title = \"to geometry\";\r\n\tLGraphToGeometry.desc = \"converts a mesh to geometry\";\r\n\r\n\tLGraphToGeometry.prototype.onExecute = function() {\r\n\t\tvar mesh = this.getInputData(0);\r\n\t\tif(!mesh)\r\n\t\t\treturn;\r\n\r\n\t\tif(mesh != this.last_mesh)\r\n\t\t{\r\n\t\t\tthis.last_mesh = mesh;\r\n\t\t\tfor(i in mesh.vertexBuffers)\r\n\t\t\t{\r\n\t\t\t\tvar buffer = mesh.vertexBuffers[i];\r\n\t\t\t\tthis.geometry[i] = buffer.data\r\n\t\t\t}\r\n\t\t\tif(mesh.indexBuffers[\"triangles\"])\r\n\t\t\t\tthis.geometry.indices = mesh.indexBuffers[\"triangles\"].data;\r\n\r\n\t\t\tthis.geometry._id = generateGeometryId();\r\n\t\t\tthis.geometry._version = 0;\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0,this.geometry);\r\n\t\tif(this.geometry)\r\n\t\t\tthis.setOutputData(1,this.geometry.vertices);\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/toGeometry\", LGraphToGeometry );\r\n\r\n\tfunction LGraphGeometryToMesh() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addOutput(\"mesh\", \"mesh\");\r\n\t\tthis.properties = {};\r\n\t\tthis.version = -1;\r\n\t\tthis.mesh = null;\r\n\t}\r\n\r\n\tLGraphGeometryToMesh.title = \"Geo to Mesh\";\r\n\r\n\tLGraphGeometryToMesh.prototype.updateMesh = function(geometry)\r\n\t{\r\n\t\tif(!this.mesh)\r\n\t\t\tthis.mesh = new GL.Mesh();\r\n\r\n\t\tfor(var i in geometry)\r\n\t\t{\r\n\t\t\tif(i[0] == \"_\")\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tvar buffer_data = geometry[i];\r\n\r\n\t\t\tvar info = GL.Mesh.common_buffers[i];\r\n\t\t\tif(!info && i != \"indices\") //unknown buffer\r\n\t\t\t\tcontinue;\r\n\t\t\tvar spacing = info ? info.spacing : 3;\r\n\t\t\tvar mesh_buffer = this.mesh.vertexBuffers[i];\r\n\r\n\t\t\tif(!mesh_buffer || mesh_buffer.data.length != buffer_data.length)\r\n\t\t\t{\r\n\t\t\t\tmesh_buffer = new GL.Buffer( i == \"indices\" ? GL.ELEMENT_ARRAY_BUFFER : GL.ARRAY_BUFFER, buffer_data, spacing, GL.DYNAMIC_DRAW );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tmesh_buffer.data.set( buffer_data );\r\n\t\t\t\tmesh_buffer.upload(GL.DYNAMIC_DRAW);\r\n\t\t\t}\r\n\r\n\t\t\tthis.mesh.addBuffer( i, mesh_buffer );\r\n\t\t}\r\n\r\n\t\tif(this.mesh.vertexBuffers.normals &&this.mesh.vertexBuffers.normals.data.length != this.mesh.vertexBuffers.vertices.data.length )\r\n\t\t{\r\n\t\t\tvar n = new Float32Array([0,1,0]);\r\n\t\t\tvar normals = new Float32Array( this.mesh.vertexBuffers.vertices.data.length );\r\n\t\t\tfor(var i = 0; i < normals.length; i+= 3)\r\n\t\t\t\tnormals.set( n, i );\r\n\t\t\tmesh_buffer = new GL.Buffer( GL.ARRAY_BUFFER, normals, 3 );\r\n\t\t\tthis.mesh.addBuffer( \"normals\", mesh_buffer );\r\n\t\t}\r\n\r\n\t\tthis.mesh.updateBoundingBox();\r\n\t\tthis.geometry_id = this.mesh.id = geometry._id;\r\n\t\tthis.version = this.mesh.version = geometry._version;\r\n\t\treturn this.mesh;\r\n\t}\r\n\r\n\tLGraphGeometryToMesh.prototype.onExecute = function() {\r\n\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\t\tif( this.version != geometry._version || this.geometry_id != geometry._id )\r\n\t\t\tthis.updateMesh( geometry );\r\n\t\tthis.setOutputData(0, this.mesh);\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/toMesh\", LGraphGeometryToMesh );\r\n\r\n\tfunction LGraphRenderMesh() {\r\n\t\tthis.addInput(\"mesh\", \"mesh\");\r\n\t\tthis.addInput(\"mat4\", \"mat4\");\r\n\t\tthis.addInput(\"tex\", \"texture\");\r\n\r\n\t\tthis.properties = {\r\n\t\t\tenabled: true,\r\n\t\t\tprimitive: GL.TRIANGLES,\r\n\t\t\tadditive: false,\r\n\t\t\tcolor: [1,1,1],\r\n\t\t\topacity: 1\r\n\t\t};\r\n\r\n\t\tthis.color = vec4.create([1,1,1,1]);\r\n\t\tthis.model_matrix = mat4.create();\r\n\t\tthis.uniforms = {\r\n\t\t\tu_color: this.color,\r\n\t\t\tu_model: this.model_matrix\r\n\t\t};\r\n\t}\r\n\r\n\tLGraphRenderMesh.title = \"Render Mesh\";\r\n\tLGraphRenderMesh.desc = \"renders a mesh flat\";\r\n\r\n\tLGraphRenderMesh.PRIMITIVE_VALUES = { \"points\":GL.POINTS, \"lines\":GL.LINES, \"line_loop\":GL.LINE_LOOP,\"line_strip\":GL.LINE_STRIP, \"triangles\":GL.TRIANGLES, \"triangle_fan\":GL.TRIANGLE_FAN, \"triangle_strip\":GL.TRIANGLE_STRIP };\r\n\r\n\tLGraphRenderMesh.widgets_info = {\r\n\t\tprimitive: { widget: \"combo\", values: LGraphRenderMesh.PRIMITIVE_VALUES },\r\n\t\tcolor: { widget: \"color\" }\r\n\t};\r\n\r\n\tLGraphRenderMesh.prototype.onExecute = function() {\r\n\r\n\t\tif(!this.properties.enabled)\r\n\t\t\treturn;\r\n\r\n\t\tvar mesh = this.getInputData(0);\r\n\t\tif(!mesh)\r\n\t\t\treturn;\r\n\r\n\t\tif(!LiteGraph.LGraphRender.onRequestCameraMatrices)\r\n\t\t{\r\n\t\t\tconsole.warn(\"cannot render geometry, LiteGraph.onRequestCameraMatrices is null, remember to fill this with a callback(view_matrix, projection_matrix,viewprojection_matrix) to use 3D rendering from the graph\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tLiteGraph.LGraphRender.onRequestCameraMatrices( view_matrix, projection_matrix,viewprojection_matrix );\r\n\t\tvar shader = null;\r\n\t\tvar texture = this.getInputData(2);\r\n\t\tif(texture)\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"textured\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"textured\"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code, { USE_TEXTURE:\"\" });\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"flat\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"flat\"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code );\r\n\t\t}\r\n\r\n\t\tthis.color.set( this.properties.color );\r\n\t\tthis.color[3] = this.properties.opacity;\r\n\r\n\t\tvar model_matrix = this.model_matrix;\r\n\t\tvar m = this.getInputData(1);\r\n\t\tif(m)\r\n\t\t\tmodel_matrix.set(m);\r\n\t\telse\r\n\t\t\tmat4.identity( model_matrix );\r\n\r\n\t\tthis.uniforms.u_point_size = 1;\r\n\t\tvar primitive = this.properties.primitive;\r\n\r\n\t\tshader.uniforms( global_uniforms );\r\n\t\tshader.uniforms( this.uniforms );\r\n\r\n\t\tif(this.properties.opacity >= 1)\r\n\t\t\tgl.disable( gl.BLEND );\r\n\t\telse\r\n\t\t\tgl.enable( gl.BLEND );\r\n\t\tgl.enable( gl.DEPTH_TEST );\r\n\t\tif( this.properties.additive )\r\n\t\t{\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE );\r\n\t\t\tgl.depthMask( false );\r\n\t\t}\r\n\t\telse\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );\r\n\r\n\t\tvar indices = \"indices\";\r\n\t\tif( mesh.indexBuffers.triangles )\r\n\t\t\tindices = \"triangles\";\r\n\t\tshader.draw( mesh, primitive, indices );\r\n\t\tgl.disable( gl.BLEND );\r\n\t\tgl.depthMask( true );\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/render_mesh\", LGraphRenderMesh );\r\n\r\n\t//**************************\r\n\r\n\r\n\tfunction LGraphGeometryPrimitive() {\r\n\t\tthis.addInput(\"size\", \"number\");\r\n\t\tthis.addOutput(\"out\", \"mesh\");\r\n\t\tthis.properties = { type: 1, size: 1, subdivisions: 32 };\r\n\r\n\t\tthis.version = (Math.random() * 100000)|0;\r\n\t\tthis.last_info = { type: -1, size: -1, subdivisions: -1 };\r\n\t}\r\n\r\n\tLGraphGeometryPrimitive.title = \"Primitive\";\r\n\r\n\tLGraphGeometryPrimitive.VALID = { \"CUBE\":1, \"PLANE\":2, \"CYLINDER\":3, \"SPHERE\":4, \"CIRCLE\":5, \"HEMISPHERE\":6, \"ICOSAHEDRON\":7, \"CONE\":8, \"QUAD\":9 };\r\n\tLGraphGeometryPrimitive.widgets_info = {\r\n\t\ttype: { widget: \"combo\", values: LGraphGeometryPrimitive.VALID }\r\n\t};\r\n\r\n\tLGraphGeometryPrimitive.prototype.onExecute = function() {\r\n\r\n\t\tif( !this.isOutputConnected(0) )\r\n\t\t\treturn;\r\n\r\n\t\tvar size = this.getInputOrProperty(\"size\");\r\n\r\n\t\t//update\r\n\t\tif( this.last_info.type != this.properties.type || this.last_info.size != size || this.last_info.subdivisions != this.properties.subdivisions )\r\n\t\t\tthis.updateMesh( this.properties.type, size, this.properties.subdivisions );\r\n\r\n\t\tthis.setOutputData(0,this._mesh);\r\n\t}\r\n\r\n\tLGraphGeometryPrimitive.prototype.updateMesh = function(type, size, subdivisions)\r\n\t{\r\n\t\tsubdivisions = Math.max(0,subdivisions)|0;\r\n\r\n\t\tswitch (type)\r\n\t\t{\r\n\t\t\tcase 1: //CUBE: \r\n\t\t\t\tthis._mesh = GL.Mesh.cube({size: size, normals:true,coords:true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 2: //PLANE:\r\n\t\t\t\tthis._mesh = GL.Mesh.plane({size: size, xz: true, detail: subdivisions, normals:true,coords:true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 3: //CYLINDER:\r\n\t\t\t\tthis._mesh = GL.Mesh.cylinder({size: size, subdivisions: subdivisions, normals:true,coords:true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 4: //SPHERE:\r\n\t\t\t\tthis._mesh = GL.Mesh.sphere({size: size, \"long\": subdivisions, lat: subdivisions, normals:true,coords:true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 5: //CIRCLE:\r\n\t\t\t\tthis._mesh = GL.Mesh.circle({size: size, slices: subdivisions, normals:true, coords:true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 6: //HEMISPHERE:\r\n\t\t\t\tthis._mesh = GL.Mesh.sphere({size: size, \"long\": subdivisions, lat: subdivisions, normals:true, coords:true, hemi: true});\r\n\t\t\t\tbreak;\r\n\t\t\tcase 7: //ICOSAHEDRON:\r\n\t\t\t\tthis._mesh = GL.Mesh.icosahedron({size: size, subdivisions:subdivisions });\r\n\t\t\t\tbreak;\r\n\t\t\tcase 8: //CONE:\r\n\t\t\t\tthis._mesh = GL.Mesh.cone({radius: size, height: size, subdivisions:subdivisions });\r\n\t\t\t\tbreak;\r\n\t\t\tcase 9: //QUAD:\r\n\t\t\t\tthis._mesh = GL.Mesh.plane({size: size, xz: false, detail: subdivisions, normals:true, coords:true });\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\r\n\t\tthis.last_info.type = type;\r\n\t\tthis.last_info.size = size;\r\n\t\tthis.last_info.subdivisions = subdivisions;\r\n\t\tthis._mesh.version = this.version++;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/mesh_primitive\", LGraphGeometryPrimitive );\r\n\r\n\r\n\tfunction LGraphRenderPoints() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addInput(\"mat4\", \"mat4\");\r\n\t\tthis.addInput(\"tex\", \"texture\");\r\n\t\tthis.properties = {\r\n\t\t\tenabled: true,\r\n\t\t\tpoint_size: 0.1,\r\n\t\t\tfixed_size: false,\r\n\t\t\tadditive: true,\r\n\t\t\tcolor: [1,1,1],\r\n\t\t\topacity: 1\r\n\t\t};\r\n\r\n\t\tthis.color = vec4.create([1,1,1,1]);\r\n\r\n\t\tthis.uniforms = {\r\n\t\t\tu_point_size: 1,\r\n\t\t\tu_perspective: 1,\r\n\t\t\tu_point_perspective: 1,\r\n\t\t\tu_color: this.color\r\n\t\t};\r\n\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.mesh = null;\r\n\t}\r\n\r\n\tLGraphRenderPoints.title = \"renderPoints\";\r\n\tLGraphRenderPoints.desc = \"render points with a texture\";\r\n\r\n\tLGraphRenderPoints.widgets_info = {\r\n\t\tcolor: { widget: \"color\" }\r\n\t};\r\n\r\n\tLGraphRenderPoints.prototype.updateMesh = function(geometry)\r\n\t{\r\n\t\tvar buffer = this.buffer;\r\n\t\tif(!this.buffer || !this.buffer.data || this.buffer.data.length != geometry.vertices.length)\r\n\t\t\tthis.buffer = new GL.Buffer( GL.ARRAY_BUFFER, geometry.vertices,3,GL.DYNAMIC_DRAW);\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.buffer.data.set( geometry.vertices );\r\n\t\t\tthis.buffer.upload(GL.DYNAMIC_DRAW);\r\n\t\t}\r\n\r\n\t\tif(!this.mesh)\r\n\t\t\tthis.mesh = new GL.Mesh();\r\n\r\n\t\tthis.mesh.addBuffer(\"vertices\",this.buffer);\r\n\t\tthis.geometry_id = this.mesh.id = geometry._id;\r\n\t\tthis.version = this.mesh.version = geometry._version;\r\n\t}\r\n\r\n\tLGraphRenderPoints.prototype.onExecute = function() {\r\n\r\n\t\tif(!this.properties.enabled)\r\n\t\t\treturn;\r\n\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\t\tif(this.version != geometry._version || this.geometry_id != geometry._id )\r\n\t\t\tthis.updateMesh( geometry );\r\n\r\n\t\tif(!LiteGraph.LGraphRender.onRequestCameraMatrices)\r\n\t\t{\r\n\t\t\tconsole.warn(\"cannot render geometry, LiteGraph.onRequestCameraMatrices is null, remember to fill this with a callback(view_matrix, projection_matrix,viewprojection_matrix) to use 3D rendering from the graph\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tLiteGraph.LGraphRender.onRequestCameraMatrices( view_matrix, projection_matrix,viewprojection_matrix );\r\n\t\tvar shader = null;\r\n\r\n\t\tvar texture = this.getInputData(2);\r\n\t\t\r\n\t\tif(texture)\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"textured_points\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"textured_points\"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code, { USE_TEXTURED_POINTS:\"\" });\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"points\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"points\"] = new GL.Shader( LGraphRenderPoints.vertex_shader_code, LGraphRenderPoints.fragment_shader_code, { USE_POINTS: \"\" });\r\n\t\t}\r\n\r\n\t\tthis.color.set( this.properties.color );\r\n\t\tthis.color[3] = this.properties.opacity;\r\n\r\n\t\tvar m = this.getInputData(1);\r\n\t\tif(m)\r\n\t\t\tmodel_matrix.set(m);\r\n\t\telse\r\n\t\t\tmat4.identity( model_matrix );\r\n\r\n\t\tthis.uniforms.u_point_size = this.properties.point_size;\r\n\t\tthis.uniforms.u_point_perspective = this.properties.fixed_size ? 0 : 1;\r\n\t\tthis.uniforms.u_perspective = gl.viewport_data[3] * projection_matrix[5];\r\n\r\n\t\tshader.uniforms( global_uniforms );\r\n\t\tshader.uniforms( this.uniforms );\r\n\r\n\t\tif(this.properties.opacity >= 1)\r\n\t\t\tgl.disable( gl.BLEND );\r\n\t\telse\r\n\t\t\tgl.enable( gl.BLEND );\r\n\r\n\t\tgl.enable( gl.DEPTH_TEST );\r\n\t\tif( this.properties.additive )\r\n\t\t{\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE );\r\n\t\t\tgl.depthMask( false );\r\n\t\t}\r\n\t\telse\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );\r\n\r\n\t\tshader.draw( this.mesh, GL.POINTS );\r\n\r\n\t\tgl.disable( gl.BLEND );\r\n\t\tgl.depthMask( true );\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/render_points\", LGraphRenderPoints );\r\n\r\n\tLGraphRenderPoints.vertex_shader_code = '\\\r\n\t\tprecision mediump float;\\n\\\r\n\t\tattribute vec3 a_vertex;\\n\\\r\n\t\tvarying vec3 v_vertex;\\n\\\r\n\t\tattribute vec3 a_normal;\\n\\\r\n\t\tvarying vec3 v_normal;\\n\\\r\n\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\tattribute vec4 a_color;\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tattribute vec2 a_coord;\\n\\\r\n\t\tvarying vec2 v_coord;\\n\\\r\n\t\t#ifdef USE_SIZE\\n\\\r\n\t\t\tattribute float a_extra;\\n\\\r\n\t\t#endif\\n\\\r\n\t\t#ifdef USE_INSTANCING\\n\\\r\n\t\t\tattribute mat4 u_model;\\n\\\r\n\t\t#else\\n\\\r\n\t\t\tuniform mat4 u_model;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tuniform mat4 u_viewprojection;\\n\\\r\n\t\tuniform float u_point_size;\\n\\\r\n\t\tuniform float u_perspective;\\n\\\r\n\t\tuniform float u_point_perspective;\\n\\\r\n\t\tfloat computePointSize(float radius, float w)\\n\\\r\n\t\t{\\n\\\r\n\t\t\tif(radius < 0.0)\\n\\\r\n\t\t\t\treturn -radius;\\n\\\r\n\t\t\treturn u_perspective * radius / w;\\n\\\r\n\t\t}\\n\\\r\n\t\tvoid main() {\\n\\\r\n\t\t\tv_coord = a_coord;\\n\\\r\n\t\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\t\tv_color = a_color;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tv_vertex = ( u_model * vec4( a_vertex, 1.0 )).xyz;\\n\\\r\n\t\t\tv_normal = ( u_model * vec4( a_normal, 0.0 )).xyz;\\n\\\r\n\t\t\tgl_Position = u_viewprojection * vec4(v_vertex,1.0);\\n\\\r\n\t\t\tgl_PointSize = u_point_size;\\n\\\r\n\t\t\t#ifdef USE_SIZE\\n\\\r\n\t\t\t\tgl_PointSize = a_extra;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tif(u_point_perspective != 0.0)\\n\\\r\n\t\t\t\tgl_PointSize = computePointSize( gl_PointSize, gl_Position.w );\\n\\\r\n\t\t}\\\r\n\t';\r\n\r\n\tLGraphRenderPoints.fragment_shader_code = '\\\r\n\t\tprecision mediump float;\\n\\\r\n\t\tuniform vec4 u_color;\\n\\\r\n\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tvarying vec2 v_coord;\\n\\\r\n\t\tuniform sampler2D u_texture;\\n\\\r\n\t\tvoid main() {\\n\\\r\n\t\t\tvec4 color = u_color;\\n\\\r\n\t\t\t#ifdef USE_TEXTURED_POINTS\\n\\\r\n\t\t\t\tcolor *= texture2D(u_texture, gl_PointCoord.xy);\\n\\\r\n\t\t\t#else\\n\\\r\n\t\t\t\t#ifdef USE_TEXTURE\\n\\\r\n\t\t\t\t  color *= texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\t  if(color.a < 0.1)\\n\\\r\n\t\t\t\t\tdiscard;\\n\\\r\n\t\t\t\t#endif\\n\\\r\n\t\t\t\t#ifdef USE_POINTS\\n\\\r\n\t\t\t\t\tfloat dist = length( gl_PointCoord.xy - vec2(0.5) );\\n\\\r\n\t\t\t\t\tif( dist > 0.45 )\\n\\\r\n\t\t\t\t\t\tdiscard;\\n\\\r\n\t\t\t\t#endif\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\t\tcolor *= v_color;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tgl_FragColor = color;\\n\\\r\n\t\t}\\\r\n\t';\r\n\r\n\t//based on https://inconvergent.net/2019/depth-of-field/\r\n\t/*\r\n\tfunction LGraphRenderGeometryDOF() {\r\n\t\tthis.addInput(\"in\", \"geometry\");\r\n\t\tthis.addInput(\"mat4\", \"mat4\");\r\n\t\tthis.addInput(\"tex\", \"texture\");\r\n\t\tthis.properties = {\r\n\t\t\tenabled: true,\r\n\t\t\tlines: true,\r\n\t\t\tpoint_size: 0.1,\r\n\t\t\tfixed_size: false,\r\n\t\t\tadditive: true,\r\n\t\t\tcolor: [1,1,1],\r\n\t\t\topacity: 1\r\n\t\t};\r\n\r\n\t\tthis.color = vec4.create([1,1,1,1]);\r\n\r\n\t\tthis.uniforms = {\r\n\t\t\tu_point_size: 1,\r\n\t\t\tu_perspective: 1,\r\n\t\t\tu_point_perspective: 1,\r\n\t\t\tu_color: this.color\r\n\t\t};\r\n\r\n\t\tthis.geometry_id = -1;\r\n\t\tthis.version = -1;\r\n\t\tthis.mesh = null;\r\n\t}\r\n\r\n\tLGraphRenderGeometryDOF.widgets_info = {\r\n\t\tcolor: { widget: \"color\" }\r\n\t};\r\n\r\n\tLGraphRenderGeometryDOF.prototype.updateMesh = function(geometry)\r\n\t{\r\n\t\tvar buffer = this.buffer;\r\n\t\tif(!this.buffer || this.buffer.data.length != geometry.vertices.length)\r\n\t\t\tthis.buffer = new GL.Buffer( GL.ARRAY_BUFFER, geometry.vertices,3,GL.DYNAMIC_DRAW);\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.buffer.data.set( geometry.vertices );\r\n\t\t\tthis.buffer.upload(GL.DYNAMIC_DRAW);\r\n\t\t}\r\n\r\n\t\tif(!this.mesh)\r\n\t\t\tthis.mesh = new GL.Mesh();\r\n\r\n\t\tthis.mesh.addBuffer(\"vertices\",this.buffer);\r\n\t\tthis.geometry_id = this.mesh.id = geometry._id;\r\n\t\tthis.version = this.mesh.version = geometry._version;\r\n\t}\r\n\r\n\tLGraphRenderGeometryDOF.prototype.onExecute = function() {\r\n\r\n\t\tif(!this.properties.enabled)\r\n\t\t\treturn;\r\n\r\n\t\tvar geometry = this.getInputData(0);\r\n\t\tif(!geometry)\r\n\t\t\treturn;\r\n\t\tif(this.version != geometry._version || this.geometry_id != geometry._id )\r\n\t\t\tthis.updateMesh( geometry );\r\n\r\n\t\tif(!LiteGraph.LGraphRender.onRequestCameraMatrices)\r\n\t\t{\r\n\t\t\tconsole.warn(\"cannot render geometry, LiteGraph.onRequestCameraMatrices is null, remember to fill this with a callback(view_matrix, projection_matrix,viewprojection_matrix) to use 3D rendering from the graph\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tLiteGraph.LGraphRender.onRequestCameraMatrices( view_matrix, projection_matrix,viewprojection_matrix );\r\n\t\tvar shader = null;\r\n\r\n\t\tvar texture = this.getInputData(2);\r\n\t\t\r\n\t\tif(texture)\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"textured_points\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"textured_points\"] = new GL.Shader( LGraphRenderGeometryDOF.vertex_shader_code, LGraphRenderGeometryDOF.fragment_shader_code, { USE_TEXTURED_POINTS:\"\" });\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tshader = gl.shaders[\"points\"];\r\n\t\t\tif(!shader)\r\n\t\t\t\tshader = gl.shaders[\"points\"] = new GL.Shader( LGraphRenderGeometryDOF.vertex_shader_code, LGraphRenderGeometryDOF.fragment_shader_code, { USE_POINTS: \"\" });\r\n\t\t}\r\n\r\n\t\tthis.color.set( this.properties.color );\r\n\t\tthis.color[3] = this.properties.opacity;\r\n\r\n\t\tvar m = this.getInputData(1);\r\n\t\tif(m)\r\n\t\t\tmodel_matrix.set(m);\r\n\t\telse\r\n\t\t\tmat4.identity( model_matrix );\r\n\r\n\t\tthis.uniforms.u_point_size = this.properties.point_size;\r\n\t\tthis.uniforms.u_point_perspective = this.properties.fixed_size ? 0 : 1;\r\n\t\tthis.uniforms.u_perspective = gl.viewport_data[3] * projection_matrix[5];\r\n\r\n\t\tshader.uniforms( global_uniforms );\r\n\t\tshader.uniforms( this.uniforms );\r\n\r\n\t\tif(this.properties.opacity >= 1)\r\n\t\t\tgl.disable( gl.BLEND );\r\n\t\telse\r\n\t\t\tgl.enable( gl.BLEND );\r\n\r\n\t\tgl.enable( gl.DEPTH_TEST );\r\n\t\tif( this.properties.additive )\r\n\t\t{\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE );\r\n\t\t\tgl.depthMask( false );\r\n\t\t}\r\n\t\telse\r\n\t\t\tgl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );\r\n\r\n\t\tshader.draw( this.mesh, GL.POINTS );\r\n\r\n\t\tgl.disable( gl.BLEND );\r\n\t\tgl.depthMask( true );\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"geometry/render_dof\", LGraphRenderGeometryDOF );\r\n\r\n\tLGraphRenderGeometryDOF.vertex_shader_code = '\\\r\n\t\tprecision mediump float;\\n\\\r\n\t\tattribute vec3 a_vertex;\\n\\\r\n\t\tvarying vec3 v_vertex;\\n\\\r\n\t\tattribute vec3 a_normal;\\n\\\r\n\t\tvarying vec3 v_normal;\\n\\\r\n\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\tattribute vec4 a_color;\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tattribute vec2 a_coord;\\n\\\r\n\t\tvarying vec2 v_coord;\\n\\\r\n\t\t#ifdef USE_SIZE\\n\\\r\n\t\t\tattribute float a_extra;\\n\\\r\n\t\t#endif\\n\\\r\n\t\t#ifdef USE_INSTANCING\\n\\\r\n\t\t\tattribute mat4 u_model;\\n\\\r\n\t\t#else\\n\\\r\n\t\t\tuniform mat4 u_model;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tuniform mat4 u_viewprojection;\\n\\\r\n\t\tuniform float u_point_size;\\n\\\r\n\t\tuniform float u_perspective;\\n\\\r\n\t\tuniform float u_point_perspective;\\n\\\r\n\t\tfloat computePointSize(float radius, float w)\\n\\\r\n\t\t{\\n\\\r\n\t\t\tif(radius < 0.0)\\n\\\r\n\t\t\t\treturn -radius;\\n\\\r\n\t\t\treturn u_perspective * radius / w;\\n\\\r\n\t\t}\\n\\\r\n\t\tvoid main() {\\n\\\r\n\t\t\tv_coord = a_coord;\\n\\\r\n\t\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\t\tv_color = a_color;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tv_vertex = ( u_model * vec4( a_vertex, 1.0 )).xyz;\\n\\\r\n\t\t\tv_normal = ( u_model * vec4( a_normal, 0.0 )).xyz;\\n\\\r\n\t\t\tgl_Position = u_viewprojection * vec4(v_vertex,1.0);\\n\\\r\n\t\t\tgl_PointSize = u_point_size;\\n\\\r\n\t\t\t#ifdef USE_SIZE\\n\\\r\n\t\t\t\tgl_PointSize = a_extra;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tif(u_point_perspective != 0.0)\\n\\\r\n\t\t\t\tgl_PointSize = computePointSize( gl_PointSize, gl_Position.w );\\n\\\r\n\t\t}\\\r\n\t';\r\n\r\n\tLGraphRenderGeometryDOF.fragment_shader_code = '\\\r\n\t\tprecision mediump float;\\n\\\r\n\t\tuniform vec4 u_color;\\n\\\r\n\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t#endif\\n\\\r\n\t\tvarying vec2 v_coord;\\n\\\r\n\t\tuniform sampler2D u_texture;\\n\\\r\n\t\tvoid main() {\\n\\\r\n\t\t\tvec4 color = u_color;\\n\\\r\n\t\t\t#ifdef USE_TEXTURED_POINTS\\n\\\r\n\t\t\t\tcolor *= texture2D(u_texture, gl_PointCoord.xy);\\n\\\r\n\t\t\t#else\\n\\\r\n\t\t\t\t#ifdef USE_TEXTURE\\n\\\r\n\t\t\t\t  color *= texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\t  if(color.a < 0.1)\\n\\\r\n\t\t\t\t\tdiscard;\\n\\\r\n\t\t\t\t#endif\\n\\\r\n\t\t\t\t#ifdef USE_POINTS\\n\\\r\n\t\t\t\t\tfloat dist = length( gl_PointCoord.xy - vec2(0.5) );\\n\\\r\n\t\t\t\t\tif( dist > 0.45 )\\n\\\r\n\t\t\t\t\t\tdiscard;\\n\\\r\n\t\t\t\t#endif\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\t#ifdef USE_COLOR\\n\\\r\n\t\t\t\tcolor *= v_color;\\n\\\r\n\t\t\t#endif\\n\\\r\n\t\t\tgl_FragColor = color;\\n\\\r\n\t\t}\\\r\n\t';\r\n\t*/\r\n\r\n\r\n\r\n})(this);"
  },
  {
    "path": "src/nodes/glfx.js",
    "content": "(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n    var LGraphTexture = global.LGraphTexture;\r\n\r\n    //Works with Litegl.js to create WebGL nodes\r\n    if (typeof GL != \"undefined\") {\r\n        // Texture Lens *****************************************\r\n        function LGraphFXLens() {\r\n            this.addInput(\"Texture\", \"Texture\");\r\n            this.addInput(\"Aberration\", \"number\");\r\n            this.addInput(\"Distortion\", \"number\");\r\n            this.addInput(\"Blur\", \"number\");\r\n            this.addOutput(\"Texture\", \"Texture\");\r\n            this.properties = {\r\n                aberration: 1.0,\r\n                distortion: 1.0,\r\n                blur: 1.0,\r\n                precision: LGraphTexture.DEFAULT\r\n            };\r\n\r\n            if (!LGraphFXLens._shader) {\r\n                LGraphFXLens._shader = new GL.Shader(\r\n                    GL.Shader.SCREEN_VERTEX_SHADER,\r\n                    LGraphFXLens.pixel_shader\r\n                );\r\n                LGraphFXLens._texture = new GL.Texture(3, 1, {\r\n                    format: gl.RGB,\r\n                    wrap: gl.CLAMP_TO_EDGE,\r\n                    magFilter: gl.LINEAR,\r\n                    minFilter: gl.LINEAR,\r\n                    pixel_data: [255, 0, 0, 0, 255, 0, 0, 0, 255]\r\n                });\r\n            }\r\n        }\r\n\r\n        LGraphFXLens.title = \"Lens\";\r\n        LGraphFXLens.desc = \"Camera Lens distortion\";\r\n        LGraphFXLens.widgets_info = {\r\n            precision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\r\n        };\r\n\r\n        LGraphFXLens.prototype.onExecute = function() {\r\n            var tex = this.getInputData(0);\r\n            if (this.properties.precision === LGraphTexture.PASS_THROUGH) {\r\n                this.setOutputData(0, tex);\r\n                return;\r\n            }\r\n\r\n            if (!tex) {\r\n                return;\r\n            }\r\n\r\n            this._tex = LGraphTexture.getTargetTexture(\r\n                tex,\r\n                this._tex,\r\n                this.properties.precision\r\n            );\r\n\r\n            var aberration = this.properties.aberration;\r\n            if (this.isInputConnected(1)) {\r\n                aberration = this.getInputData(1);\r\n                this.properties.aberration = aberration;\r\n            }\r\n\r\n            var distortion = this.properties.distortion;\r\n            if (this.isInputConnected(2)) {\r\n                distortion = this.getInputData(2);\r\n                this.properties.distortion = distortion;\r\n            }\r\n\r\n            var blur = this.properties.blur;\r\n            if (this.isInputConnected(3)) {\r\n                blur = this.getInputData(3);\r\n                this.properties.blur = blur;\r\n            }\r\n\r\n            gl.disable(gl.BLEND);\r\n            gl.disable(gl.DEPTH_TEST);\r\n            var mesh = Mesh.getScreenQuad();\r\n            var shader = LGraphFXLens._shader;\r\n            //var camera = LS.Renderer._current_camera;\r\n\r\n            this._tex.drawTo(function() {\r\n                tex.bind(0);\r\n                shader\r\n                    .uniforms({\r\n                        u_texture: 0,\r\n                        u_aberration: aberration,\r\n                        u_distortion: distortion,\r\n                        u_blur: blur\r\n                    })\r\n                    .draw(mesh);\r\n            });\r\n\r\n            this.setOutputData(0, this._tex);\r\n        };\r\n\r\n        LGraphFXLens.pixel_shader =\r\n            \"precision highp float;\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec2 u_camera_planes;\\n\\\r\n\t\t\tuniform float u_aberration;\\n\\\r\n\t\t\tuniform float u_distortion;\\n\\\r\n\t\t\tuniform float u_blur;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec2 coord = v_coord;\\n\\\r\n\t\t\t\tfloat dist = distance(vec2(0.5), coord);\\n\\\r\n\t\t\t\tvec2 dist_coord = coord - vec2(0.5);\\n\\\r\n\t\t\t\tfloat percent = 1.0 + ((0.5 - dist) / 0.5) * u_distortion;\\n\\\r\n\t\t\t\tdist_coord *= percent;\\n\\\r\n\t\t\t\tcoord = dist_coord + vec2(0.5);\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture,coord, u_blur * dist);\\n\\\r\n\t\t\t\tcolor.r = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0+0.01*u_aberration), u_blur * dist ).r;\\n\\\r\n\t\t\t\tcolor.b = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0-0.01*u_aberration), u_blur * dist ).b;\\n\\\r\n\t\t\t\tgl_FragColor = color;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n        /*\r\n\t\t\tfloat normalized_tunable_sigmoid(float xs, float k)\\n\\\r\n\t\t\t{\\n\\\r\n\t\t\t\txs = xs * 2.0 - 1.0;\\n\\\r\n\t\t\t\tfloat signx = sign(xs);\\n\\\r\n\t\t\t\tfloat absx = abs(xs);\\n\\\r\n\t\t\t\treturn signx * ((-k - 1.0)*absx)/(2.0*(-2.0*k*absx+k-1.0)) + 0.5;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t*/\r\n\r\n        LiteGraph.registerNodeType(\"fx/lens\", LGraphFXLens);\r\n        global.LGraphFXLens = LGraphFXLens;\r\n\r\n        /* not working yet\r\n\tfunction LGraphDepthOfField()\r\n\t{\r\n\t\tthis.addInput(\"Color\",\"Texture\");\r\n\t\tthis.addInput(\"Linear Depth\",\"Texture\");\r\n\t\tthis.addInput(\"Camera\",\"camera\");\r\n\t\tthis.addOutput(\"Texture\",\"Texture\");\r\n\t\tthis.properties = { high_precision: false };\r\n\t}\r\n\r\n\tLGraphDepthOfField.title = \"Depth Of Field\";\r\n\tLGraphDepthOfField.desc = \"Applies a depth of field effect\";\r\n\r\n\tLGraphDepthOfField.prototype.onExecute = function()\r\n\t{\r\n\t\tvar tex = this.getInputData(0);\r\n\t\tvar depth = this.getInputData(1);\r\n\t\tvar camera = this.getInputData(2);\r\n\r\n\t\tif(!tex || !depth || !camera) \r\n\t\t{\r\n\t\t\tthis.setOutputData(0, tex);\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar precision = gl.UNSIGNED_BYTE;\r\n\t\tif(this.properties.high_precision)\r\n\t\t\tprecision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT;\t\t\t\r\n\t\tif(!this._temp_texture || this._temp_texture.type != precision ||\r\n\t\t\tthis._temp_texture.width != tex.width || this._temp_texture.height != tex.height)\r\n\t\t\tthis._temp_texture = new GL.Texture( tex.width, tex.height, { type: precision, format: gl.RGBA, filter: gl.LINEAR });\r\n\r\n\t\tvar shader = LGraphDepthOfField._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphDepthOfField._pixel_shader );\r\n\r\n\t\tvar screen_mesh = Mesh.getScreenQuad();\r\n\r\n\t\tgl.disable( gl.DEPTH_TEST );\r\n\t\tgl.disable( gl.BLEND );\r\n\r\n\t\tvar camera_position = camera.getEye();\r\n\t\tvar focus_point = camera.getCenter();\r\n\t\tvar distance = vec3.distance( camera_position, focus_point );\r\n\t\tvar far = camera.far;\r\n\t\tvar focus_range = distance * 0.5;\r\n\r\n\t\tthis._temp_texture.drawTo( function() {\r\n\t\t\ttex.bind(0);\r\n\t\t\tdepth.bind(1);\r\n\t\t\tshader.uniforms({u_texture:0, u_depth_texture:1, u_resolution: [1/tex.width, 1/tex.height], u_far: far, u_focus_point: distance, u_focus_scale: focus_range }).draw(screen_mesh);\r\n\t\t});\r\n\r\n\t\tthis.setOutputData(0, this._temp_texture);\r\n\t}\r\n\r\n\t//from http://tuxedolabs.blogspot.com.es/2018/05/bokeh-depth-of-field-in-single-pass.html\r\n\tLGraphDepthOfField._pixel_shader = \"\\n\\\r\n\t\tprecision highp float;\\n\\\r\n\t\tvarying vec2 v_coord;\\n\\\r\n\t\tuniform sampler2D u_texture; //Image to be processed\\n\\\r\n\t\tuniform sampler2D u_depth_texture; //Linear depth, where 1.0 == far plane\\n\\\r\n\t\tuniform vec2 u_iresolution; //The size of a pixel: vec2(1.0/width, 1.0/height)\\n\\\r\n\t\tuniform float u_far; // Far plane\\n\\\r\n\t\tuniform float u_focus_point;\\n\\\r\n\t\tuniform float u_focus_scale;\\n\\\r\n\t\t\\n\\\r\n\t\tconst float GOLDEN_ANGLE = 2.39996323;\\n\\\r\n\t\tconst float MAX_BLUR_SIZE = 20.0;\\n\\\r\n\t\tconst float RAD_SCALE = 0.5; // Smaller = nicer blur, larger = faster\\n\\\r\n\t\t\\n\\\r\n\t\tfloat getBlurSize(float depth, float focusPoint, float focusScale)\\n\\\r\n\t\t{\\n\\\r\n\t\t float coc = clamp((1.0 / focusPoint - 1.0 / depth)*focusScale, -1.0, 1.0);\\n\\\r\n\t\t return abs(coc) * MAX_BLUR_SIZE;\\n\\\r\n\t\t}\\n\\\r\n\t\t\\n\\\r\n\t\tvec3 depthOfField(vec2 texCoord, float focusPoint, float focusScale)\\n\\\r\n\t\t{\\n\\\r\n\t\t float centerDepth = texture2D(u_depth_texture, texCoord).r * u_far;\\n\\\r\n\t\t float centerSize = getBlurSize(centerDepth, focusPoint, focusScale);\\n\\\r\n\t\t vec3 color = texture2D(u_texture, v_coord).rgb;\\n\\\r\n\t\t float tot = 1.0;\\n\\\r\n\t\t\\n\\\r\n\t\t float radius = RAD_SCALE;\\n\\\r\n\t\t for (float ang = 0.0; ang < 100.0; ang += GOLDEN_ANGLE)\\n\\\r\n\t\t {\\n\\\r\n\t\t  vec2 tc = texCoord + vec2(cos(ang), sin(ang)) * u_iresolution * radius;\\n\\\r\n\t\t\t\\n\\\r\n\t\t  vec3 sampleColor = texture2D(u_texture, tc).rgb;\\n\\\r\n\t\t  float sampleDepth = texture2D(u_depth_texture, tc).r * u_far;\\n\\\r\n\t\t  float sampleSize = getBlurSize( sampleDepth, focusPoint, focusScale );\\n\\\r\n\t\t  if (sampleDepth > centerDepth)\\n\\\r\n\t\t   sampleSize = clamp(sampleSize, 0.0, centerSize*2.0);\\n\\\r\n\t\t\t\\n\\\r\n\t\t  float m = smoothstep(radius-0.5, radius+0.5, sampleSize);\\n\\\r\n\t\t  color += mix(color/tot, sampleColor, m);\\n\\\r\n\t\t  tot += 1.0;\\n\\\r\n\t\t  radius += RAD_SCALE/radius;\\n\\\r\n\t\t  if(radius>=MAX_BLUR_SIZE)\\n\\\r\n\t\t\t return color / tot;\\n\\\r\n\t\t }\\n\\\r\n\t\t return color / tot;\\n\\\r\n\t\t}\\n\\\r\n\t\tvoid main()\\n\\\r\n\t\t{\\n\\\r\n\t\t\tgl_FragColor = vec4( depthOfField( v_coord, u_focus_point, u_focus_scale ), 1.0 );\\n\\\r\n\t\t\t//gl_FragColor = vec4( texture2D(u_depth_texture, v_coord).r );\\n\\\r\n\t\t}\\n\\\r\n\t\t\";\r\n\r\n\tLiteGraph.registerNodeType(\"fx/DOF\", LGraphDepthOfField );\r\n\tglobal.LGraphDepthOfField = LGraphDepthOfField;\r\n\t*/\r\n\r\n        //*******************************************************\r\n\r\n        function LGraphFXBokeh() {\r\n            this.addInput(\"Texture\", \"Texture\");\r\n            this.addInput(\"Blurred\", \"Texture\");\r\n            this.addInput(\"Mask\", \"Texture\");\r\n            this.addInput(\"Threshold\", \"number\");\r\n            this.addOutput(\"Texture\", \"Texture\");\r\n            this.properties = {\r\n                shape: \"\",\r\n                size: 10,\r\n                alpha: 1.0,\r\n                threshold: 1.0,\r\n                high_precision: false\r\n            };\r\n        }\r\n\r\n        LGraphFXBokeh.title = \"Bokeh\";\r\n        LGraphFXBokeh.desc = \"applies an Bokeh effect\";\r\n\r\n        LGraphFXBokeh.widgets_info = { shape: { widget: \"texture\" } };\r\n\r\n        LGraphFXBokeh.prototype.onExecute = function() {\r\n            var tex = this.getInputData(0);\r\n            var blurred_tex = this.getInputData(1);\r\n            var mask_tex = this.getInputData(2);\r\n            if (!tex || !mask_tex || !this.properties.shape) {\r\n                this.setOutputData(0, tex);\r\n                return;\r\n            }\r\n\r\n            if (!blurred_tex) {\r\n                blurred_tex = tex;\r\n            }\r\n\r\n            var shape_tex = LGraphTexture.getTexture(this.properties.shape);\r\n            if (!shape_tex) {\r\n                return;\r\n            }\r\n\r\n            var threshold = this.properties.threshold;\r\n            if (this.isInputConnected(3)) {\r\n                threshold = this.getInputData(3);\r\n                this.properties.threshold = threshold;\r\n            }\r\n\r\n            var precision = gl.UNSIGNED_BYTE;\r\n            if (this.properties.high_precision) {\r\n                precision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT;\r\n            }\r\n            if (\r\n                !this._temp_texture ||\r\n                this._temp_texture.type != precision ||\r\n                this._temp_texture.width != tex.width ||\r\n                this._temp_texture.height != tex.height\r\n            ) {\r\n                this._temp_texture = new GL.Texture(tex.width, tex.height, {\r\n                    type: precision,\r\n                    format: gl.RGBA,\r\n                    filter: gl.LINEAR\r\n                });\r\n            }\r\n\r\n            //iterations\r\n            var size = this.properties.size;\r\n\r\n            var first_shader = LGraphFXBokeh._first_shader;\r\n            if (!first_shader) {\r\n                first_shader = LGraphFXBokeh._first_shader = new GL.Shader(\r\n                    Shader.SCREEN_VERTEX_SHADER,\r\n                    LGraphFXBokeh._first_pixel_shader\r\n                );\r\n            }\r\n\r\n            var second_shader = LGraphFXBokeh._second_shader;\r\n            if (!second_shader) {\r\n                second_shader = LGraphFXBokeh._second_shader = new GL.Shader(\r\n                    LGraphFXBokeh._second_vertex_shader,\r\n                    LGraphFXBokeh._second_pixel_shader\r\n                );\r\n            }\r\n\r\n            var points_mesh = this._points_mesh;\r\n            if (\r\n                !points_mesh ||\r\n                points_mesh._width != tex.width ||\r\n                points_mesh._height != tex.height ||\r\n                points_mesh._spacing != 2\r\n            ) {\r\n                points_mesh = this.createPointsMesh(tex.width, tex.height, 2);\r\n            }\r\n\r\n            var screen_mesh = Mesh.getScreenQuad();\r\n\r\n            var point_size = this.properties.size;\r\n            var min_light = this.properties.min_light;\r\n            var alpha = this.properties.alpha;\r\n\r\n            gl.disable(gl.DEPTH_TEST);\r\n            gl.disable(gl.BLEND);\r\n\r\n            this._temp_texture.drawTo(function() {\r\n                tex.bind(0);\r\n                blurred_tex.bind(1);\r\n                mask_tex.bind(2);\r\n                first_shader\r\n                    .uniforms({\r\n                        u_texture: 0,\r\n                        u_texture_blur: 1,\r\n                        u_mask: 2,\r\n                        u_texsize: [tex.width, tex.height]\r\n                    })\r\n                    .draw(screen_mesh);\r\n            });\r\n\r\n            this._temp_texture.drawTo(function() {\r\n                //clear because we use blending\r\n                //gl.clearColor(0.0,0.0,0.0,1.0);\r\n                //gl.clear( gl.COLOR_BUFFER_BIT );\r\n                gl.enable(gl.BLEND);\r\n                gl.blendFunc(gl.ONE, gl.ONE);\r\n\r\n                tex.bind(0);\r\n                shape_tex.bind(3);\r\n                second_shader\r\n                    .uniforms({\r\n                        u_texture: 0,\r\n                        u_mask: 2,\r\n                        u_shape: 3,\r\n                        u_alpha: alpha,\r\n                        u_threshold: threshold,\r\n                        u_pointSize: point_size,\r\n                        u_itexsize: [1.0 / tex.width, 1.0 / tex.height]\r\n                    })\r\n                    .draw(points_mesh, gl.POINTS);\r\n            });\r\n\r\n            this.setOutputData(0, this._temp_texture);\r\n        };\r\n\r\n        LGraphFXBokeh.prototype.createPointsMesh = function(\r\n            width,\r\n            height,\r\n            spacing\r\n        ) {\r\n            var nwidth = Math.round(width / spacing);\r\n            var nheight = Math.round(height / spacing);\r\n\r\n            var vertices = new Float32Array(nwidth * nheight * 2);\r\n\r\n            var ny = -1;\r\n            var dx = (2 / width) * spacing;\r\n            var dy = (2 / height) * spacing;\r\n            for (var y = 0; y < nheight; ++y) {\r\n                var nx = -1;\r\n                for (var x = 0; x < nwidth; ++x) {\r\n                    var pos = y * nwidth * 2 + x * 2;\r\n                    vertices[pos] = nx;\r\n                    vertices[pos + 1] = ny;\r\n                    nx += dx;\r\n                }\r\n                ny += dy;\r\n            }\r\n\r\n            this._points_mesh = GL.Mesh.load({ vertices2D: vertices });\r\n            this._points_mesh._width = width;\r\n            this._points_mesh._height = height;\r\n            this._points_mesh._spacing = spacing;\r\n\r\n            return this._points_mesh;\r\n        };\r\n\r\n        /*\r\n\tLGraphTextureBokeh._pixel_shader = \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 a_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform sampler2D u_shape;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D( u_texture, gl_PointCoord );\\n\\\r\n\t\t\t\tcolor *= v_color * u_alpha;\\n\\\r\n\t\t\t\tgl_FragColor = color;\\n\\\r\n\t\t\t}\\n\";\r\n\t*/\r\n\r\n        LGraphFXBokeh._first_pixel_shader =\r\n            \"precision highp float;\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform sampler2D u_texture_blur;\\n\\\r\n\t\t\tuniform sampler2D u_mask;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tvec4 blurred_color = texture2D(u_texture_blur, v_coord);\\n\\\r\n\t\t\t\tfloat mask = texture2D(u_mask, v_coord).x;\\n\\\r\n\t\t\t   gl_FragColor = mix(color, blurred_color, mask);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\n        LGraphFXBokeh._second_vertex_shader =\r\n            \"precision highp float;\\n\\\r\n\t\t\tattribute vec2 a_vertex2D;\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform sampler2D u_mask;\\n\\\r\n\t\t\tuniform vec2 u_itexsize;\\n\\\r\n\t\t\tuniform float u_pointSize;\\n\\\r\n\t\t\tuniform float u_threshold;\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec2 coord = a_vertex2D * 0.5 + 0.5;\\n\\\r\n\t\t\t\tv_color = texture2D( u_texture, coord );\\n\\\r\n\t\t\t\tv_color += texture2D( u_texture, coord + vec2(u_itexsize.x, 0.0) );\\n\\\r\n\t\t\t\tv_color += texture2D( u_texture, coord + vec2(0.0, u_itexsize.y));\\n\\\r\n\t\t\t\tv_color += texture2D( u_texture, coord + u_itexsize);\\n\\\r\n\t\t\t\tv_color *= 0.25;\\n\\\r\n\t\t\t\tfloat mask = texture2D(u_mask, coord).x;\\n\\\r\n\t\t\t\tfloat luminance = length(v_color) * mask;\\n\\\r\n\t\t\t\t/*luminance /= (u_pointSize*u_pointSize)*0.01 */;\\n\\\r\n\t\t\t\tluminance -= u_threshold;\\n\\\r\n\t\t\t\tif(luminance < 0.0)\\n\\\r\n\t\t\t\t{\\n\\\r\n\t\t\t\t\tgl_Position.x = -100.0;\\n\\\r\n\t\t\t\t\treturn;\\n\\\r\n\t\t\t\t}\\n\\\r\n\t\t\t\tgl_PointSize = u_pointSize;\\n\\\r\n\t\t\t\tgl_Position = vec4(a_vertex2D,0.0,1.0);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\n        LGraphFXBokeh._second_pixel_shader =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec4 v_color;\\n\\\r\n\t\t\tuniform sampler2D u_shape;\\n\\\r\n\t\t\tuniform float u_alpha;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D( u_shape, gl_PointCoord );\\n\\\r\n\t\t\t\tcolor *= v_color * u_alpha;\\n\\\r\n\t\t\t\tgl_FragColor = color;\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LiteGraph.registerNodeType(\"fx/bokeh\", LGraphFXBokeh);\r\n        global.LGraphFXBokeh = LGraphFXBokeh;\r\n\r\n        //************************************************\r\n\r\n        function LGraphFXGeneric() {\r\n            this.addInput(\"Texture\", \"Texture\");\r\n            this.addInput(\"value1\", \"number\");\r\n            this.addInput(\"value2\", \"number\");\r\n            this.addOutput(\"Texture\", \"Texture\");\r\n            this.properties = {\r\n                fx: \"halftone\",\r\n                value1: 1,\r\n                value2: 1,\r\n                precision: LGraphTexture.DEFAULT\r\n            };\r\n        }\r\n\r\n        LGraphFXGeneric.title = \"FX\";\r\n        LGraphFXGeneric.desc = \"applies an FX from a list\";\r\n\r\n        LGraphFXGeneric.widgets_info = {\r\n            fx: {\r\n                widget: \"combo\",\r\n                values: [\"halftone\", \"pixelate\", \"lowpalette\", \"noise\", \"gamma\"]\r\n            },\r\n            precision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\r\n        };\r\n        LGraphFXGeneric.shaders = {};\r\n\r\n        LGraphFXGeneric.prototype.onExecute = function() {\r\n            if (!this.isOutputConnected(0)) {\r\n                return;\r\n            } //saves work\r\n\r\n            var tex = this.getInputData(0);\r\n            if (this.properties.precision === LGraphTexture.PASS_THROUGH) {\r\n                this.setOutputData(0, tex);\r\n                return;\r\n            }\r\n\r\n            if (!tex) {\r\n                return;\r\n            }\r\n\r\n            this._tex = LGraphTexture.getTargetTexture(\r\n                tex,\r\n                this._tex,\r\n                this.properties.precision\r\n            );\r\n\r\n            //iterations\r\n            var value1 = this.properties.value1;\r\n            if (this.isInputConnected(1)) {\r\n                value1 = this.getInputData(1);\r\n                this.properties.value1 = value1;\r\n            }\r\n\r\n            var value2 = this.properties.value2;\r\n            if (this.isInputConnected(2)) {\r\n                value2 = this.getInputData(2);\r\n                this.properties.value2 = value2;\r\n            }\r\n\r\n            var fx = this.properties.fx;\r\n            var shader = LGraphFXGeneric.shaders[fx];\r\n            if (!shader) {\r\n                var pixel_shader_code = LGraphFXGeneric[\"pixel_shader_\" + fx];\r\n                if (!pixel_shader_code) {\r\n                    return;\r\n                }\r\n\r\n                shader = LGraphFXGeneric.shaders[fx] = new GL.Shader(\r\n                    Shader.SCREEN_VERTEX_SHADER,\r\n                    pixel_shader_code\r\n                );\r\n            }\r\n\r\n            gl.disable(gl.BLEND);\r\n            gl.disable(gl.DEPTH_TEST);\r\n            var mesh = Mesh.getScreenQuad();\r\n            var camera = global.LS ? LS.Renderer._current_camera : null;\r\n            var camera_planes;\r\n            if (camera) {\r\n                camera_planes = [\r\n                    LS.Renderer._current_camera.near,\r\n                    LS.Renderer._current_camera.far\r\n                ];\r\n            } else {\r\n                camera_planes = [1, 100];\r\n            }\r\n\r\n            var noise = null;\r\n            if (fx == \"noise\") {\r\n                noise = LGraphTexture.getNoiseTexture();\r\n            }\r\n\r\n            this._tex.drawTo(function() {\r\n                tex.bind(0);\r\n                if (fx == \"noise\") {\r\n                    noise.bind(1);\r\n                }\r\n\r\n                shader\r\n                    .uniforms({\r\n                        u_texture: 0,\r\n                        u_noise: 1,\r\n                        u_size: [tex.width, tex.height],\r\n                        u_rand: [Math.random(), Math.random()],\r\n                        u_value1: value1,\r\n                        u_value2: value2,\r\n                        u_camera_planes: camera_planes\r\n                    })\r\n                    .draw(mesh);\r\n            });\r\n\r\n            this.setOutputData(0, this._tex);\r\n        };\r\n\r\n        LGraphFXGeneric.pixel_shader_halftone =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec2 u_camera_planes;\\n\\\r\n\t\t\tuniform vec2 u_size;\\n\\\r\n\t\t\tuniform float u_value1;\\n\\\r\n\t\t\tuniform float u_value2;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tfloat pattern() {\\n\\\r\n\t\t\t\tfloat s = sin(u_value1 * 3.1415), c = cos(u_value1 * 3.1415);\\n\\\r\n\t\t\t\tvec2 tex = v_coord * u_size.xy;\\n\\\r\n\t\t\t\tvec2 point = vec2(\\n\\\r\n\t\t\t\t   c * tex.x - s * tex.y ,\\n\\\r\n\t\t\t\t   s * tex.x + c * tex.y \\n\\\r\n\t\t\t\t) * u_value2;\\n\\\r\n\t\t\t\treturn (sin(point.x) * sin(point.y)) * 4.0;\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tfloat average = (color.r + color.g + color.b) / 3.0;\\n\\\r\n\t\t\t\tgl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LGraphFXGeneric.pixel_shader_pixelate =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec2 u_camera_planes;\\n\\\r\n\t\t\tuniform vec2 u_size;\\n\\\r\n\t\t\tuniform float u_value1;\\n\\\r\n\t\t\tuniform float u_value2;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec2 coord = vec2( floor(v_coord.x * u_value1) / u_value1, floor(v_coord.y * u_value2) / u_value2 );\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, coord);\\n\\\r\n\t\t\t\tgl_FragColor = color;\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LGraphFXGeneric.pixel_shader_lowpalette =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform vec2 u_camera_planes;\\n\\\r\n\t\t\tuniform vec2 u_size;\\n\\\r\n\t\t\tuniform float u_value1;\\n\\\r\n\t\t\tuniform float u_value2;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tgl_FragColor = floor(color * u_value1) / u_value1;\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LGraphFXGeneric.pixel_shader_noise =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform sampler2D u_noise;\\n\\\r\n\t\t\tuniform vec2 u_size;\\n\\\r\n\t\t\tuniform float u_value1;\\n\\\r\n\t\t\tuniform float u_value2;\\n\\\r\n\t\t\tuniform vec2 u_rand;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tvec3 noise = texture2D(u_noise, v_coord * vec2(u_size.x / 512.0, u_size.y / 512.0) + u_rand).xyz - vec3(0.5);\\n\\\r\n\t\t\t\tgl_FragColor = vec4( color.xyz + noise * u_value1, color.a );\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LGraphFXGeneric.pixel_shader_gamma =\r\n            \"precision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform float u_value1;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tfloat gamma = 1.0 / u_value1;\\n\\\r\n\t\t\t\tgl_FragColor = vec4( pow( color.xyz, vec3(gamma) ), color.a );\\n\\\r\n\t\t\t}\\n\";\r\n\r\n        LiteGraph.registerNodeType(\"fx/generic\", LGraphFXGeneric);\r\n        global.LGraphFXGeneric = LGraphFXGeneric;\r\n\r\n        // Vigneting ************************************\r\n\r\n        function LGraphFXVigneting() {\r\n            this.addInput(\"Tex.\", \"Texture\");\r\n            this.addInput(\"intensity\", \"number\");\r\n\r\n            this.addOutput(\"Texture\", \"Texture\");\r\n            this.properties = {\r\n                intensity: 1,\r\n                invert: false,\r\n                precision: LGraphTexture.DEFAULT\r\n            };\r\n\r\n            if (!LGraphFXVigneting._shader) {\r\n                LGraphFXVigneting._shader = new GL.Shader(\r\n                    Shader.SCREEN_VERTEX_SHADER,\r\n                    LGraphFXVigneting.pixel_shader\r\n                );\r\n            }\r\n        }\r\n\r\n        LGraphFXVigneting.title = \"Vigneting\";\r\n        LGraphFXVigneting.desc = \"Vigneting\";\r\n\r\n        LGraphFXVigneting.widgets_info = {\r\n            precision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\r\n        };\r\n\r\n        LGraphFXVigneting.prototype.onExecute = function() {\r\n            var tex = this.getInputData(0);\r\n\r\n            if (this.properties.precision === LGraphTexture.PASS_THROUGH) {\r\n                this.setOutputData(0, tex);\r\n                return;\r\n            }\r\n\r\n            if (!tex) {\r\n                return;\r\n            }\r\n\r\n            this._tex = LGraphTexture.getTargetTexture(\r\n                tex,\r\n                this._tex,\r\n                this.properties.precision\r\n            );\r\n\r\n            var intensity = this.properties.intensity;\r\n            if (this.isInputConnected(1)) {\r\n                intensity = this.getInputData(1);\r\n                this.properties.intensity = intensity;\r\n            }\r\n\r\n            gl.disable(gl.BLEND);\r\n            gl.disable(gl.DEPTH_TEST);\r\n\r\n            var mesh = Mesh.getScreenQuad();\r\n            var shader = LGraphFXVigneting._shader;\r\n            var invert = this.properties.invert;\r\n\r\n            this._tex.drawTo(function() {\r\n                tex.bind(0);\r\n                shader\r\n                    .uniforms({\r\n                        u_texture: 0,\r\n                        u_intensity: intensity,\r\n                        u_isize: [1 / tex.width, 1 / tex.height],\r\n                        u_invert: invert ? 1 : 0\r\n                    })\r\n                    .draw(mesh);\r\n            });\r\n\r\n            this.setOutputData(0, this._tex);\r\n        };\r\n\r\n        LGraphFXVigneting.pixel_shader =\r\n            \"precision highp float;\\n\\\r\n\t\t\tprecision highp float;\\n\\\r\n\t\t\tvarying vec2 v_coord;\\n\\\r\n\t\t\tuniform sampler2D u_texture;\\n\\\r\n\t\t\tuniform float u_intensity;\\n\\\r\n\t\t\tuniform int u_invert;\\n\\\r\n\t\t\t\\n\\\r\n\t\t\tvoid main() {\\n\\\r\n\t\t\t\tfloat luminance = 1.0 - length( v_coord - vec2(0.5) ) * 1.414;\\n\\\r\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\\n\\\r\n\t\t\t\tif(u_invert == 1)\\n\\\r\n\t\t\t\t\tluminance = 1.0 - luminance;\\n\\\r\n\t\t\t\tluminance = mix(1.0, luminance, u_intensity);\\n\\\r\n\t\t\t   gl_FragColor = vec4( luminance * color.xyz, color.a);\\n\\\r\n\t\t\t}\\n\\\r\n\t\t\t\";\r\n\r\n        LiteGraph.registerNodeType(\"fx/vigneting\", LGraphFXVigneting);\r\n        global.LGraphFXVigneting = LGraphFXVigneting;\r\n    }\r\n})(this);\r\n"
  },
  {
    "path": "src/nodes/glshaders.js",
    "content": "(function(global) {\r\n\r\n    if (typeof GL == \"undefined\")\r\n\t\treturn;\r\n\r\n    var LiteGraph = global.LiteGraph;\r\n\tvar LGraphCanvas = global.LGraphCanvas;\r\n\r\n\tvar SHADERNODES_COLOR = \"#345\";\r\n\r\n\tvar LGShaders = LiteGraph.Shaders = {};\r\n\r\n\tvar GLSL_types = LGShaders.GLSL_types = [\"float\",\"vec2\",\"vec3\",\"vec4\",\"mat3\",\"mat4\",\"sampler2D\",\"samplerCube\"];\r\n\tvar GLSL_types_const = LGShaders.GLSL_types_const = [\"float\",\"vec2\",\"vec3\",\"vec4\"];\r\n\r\n\tvar GLSL_functions_desc = {\r\n\t\t\"radians\": \"T radians(T degrees)\",\r\n\t\t\"degrees\": \"T degrees(T radians)\",\r\n\t\t\"sin\": \"T sin(T angle)\",\r\n\t\t\"cos\": \"T cos(T angle)\",\r\n\t\t\"tan\": \"T tan(T angle)\",\r\n\t\t\"asin\": \"T asin(T x)\",\r\n\t\t\"acos\": \"T acos(T x)\",\r\n\t\t\"atan\": \"T atan(T x)\",\r\n\t\t\"atan2\": \"T atan(T x,T y)\",\r\n\t\t\"pow\": \"T pow(T x,T y)\",\r\n\t\t\"exp\": \"T exp(T x)\",\r\n\t\t\"log\": \"T log(T x)\",\r\n\t\t\"exp2\": \"T exp2(T x)\",\r\n\t\t\"log2\": \"T log2(T x)\",\r\n\t\t\"sqrt\": \"T sqrt(T x)\",\r\n\t\t\"inversesqrt\": \"T inversesqrt(T x)\",\r\n\t\t\"abs\": \"T abs(T x)\",\r\n\t\t\"sign\": \"T sign(T x)\",\r\n\t\t\"floor\": \"T floor(T x)\",\r\n\t\t\"round\": \"T round(T x)\",\r\n\t\t\"ceil\": \"T ceil(T x)\",\r\n\t\t\"fract\": \"T fract(T x)\",\r\n\t\t\"mod\": \"T mod(T x,T y)\", //\"T mod(T x,float y)\"\r\n\t\t\"min\": \"T min(T x,T y)\",\r\n\t\t\"max\": \"T max(T x,T y)\",\r\n\t\t\"clamp\": \"T clamp(T x,T minVal = 0.0,T maxVal = 1.0)\",\r\n\t\t\"mix\": \"T mix(T x,T y,T a)\", //\"T mix(T x,T y,float a)\"\r\n\t\t\"step\": \"T step(T edge, T edge2, T x)\", //\"T step(float edge, T x)\"\r\n\t\t\"smoothstep\": \"T smoothstep(T edge, T edge2, T x)\", //\"T smoothstep(float edge, T x)\"\r\n\t\t\"length\":\"float length(T x)\",\r\n\t\t\"distance\":\"float distance(T p0, T p1)\",\r\n\t\t\"normalize\":\"T normalize(T x)\",\r\n\t\t\"dot\": \"float dot(T x,T y)\",\r\n\t\t\"cross\": \"vec3 cross(vec3 x,vec3 y)\",\r\n\t\t\"reflect\": \"vec3 reflect(vec3 V,vec3 N)\",\r\n\t\t\"refract\": \"vec3 refract(vec3 V,vec3 N, float IOR)\"\r\n\t};\r\n\r\n\t//parse them\r\n\tvar GLSL_functions = {};\r\n\tvar GLSL_functions_name = [];\r\n\tparseGLSLDescriptions();\r\n\r\n\tLGShaders.ALL_TYPES = \"float,vec2,vec3,vec4\";\r\n\r\n\tfunction parseGLSLDescriptions()\r\n\t{\r\n\t\tGLSL_functions_name.length = 0;\r\n\r\n\t\tfor(var i in GLSL_functions_desc)\r\n\t\t{\r\n\t\t\tvar op = GLSL_functions_desc[i];\r\n\t\t\tvar index = op.indexOf(\" \");\r\n\t\t\tvar return_type = op.substr(0,index);\r\n\t\t\tvar index2 = op.indexOf(\"(\",index);\r\n\t\t\tvar func_name = op.substr(index,index2-index).trim();\r\n\t\t\tvar params = op.substr(index2 + 1, op.length - index2 - 2).split(\",\");\r\n\t\t\tfor(var j in params)\r\n\t\t\t{\r\n\t\t\t\tvar p = params[j].split(\" \").filter(function(a){ return a; });\r\n\t\t\t\tparams[j] = { type: p[0].trim(), name: p[1].trim() };\r\n\t\t\t\tif(p[2] == \"=\")\r\n\t\t\t\t\tparams[j].value = p[3].trim();\r\n\t\t\t}\r\n\t\t\tGLSL_functions[i] = { return_type: return_type, func: func_name, params: params };\r\n\t\t\tGLSL_functions_name.push( func_name );\r\n\t\t\t//console.log( GLSL_functions[i] );\r\n\t\t}\r\n\t}\r\n\r\n\t//common actions to all shader node classes\r\n\tfunction registerShaderNode( type, node_ctor )\r\n\t{\r\n\t\t//static attributes\r\n\t\tnode_ctor.color = SHADERNODES_COLOR;\r\n\t\tnode_ctor.filter = \"shader\";\r\n\r\n\t\t//common methods\r\n\t\tnode_ctor.prototype.clearDestination = function(){ this.shader_destination = {};  }\r\n\t\tnode_ctor.prototype.propagateDestination = function propagateDestination( dest_name )\r\n\t\t{\r\n\t\t\tthis.shader_destination[ dest_name ] = true;\r\n\t\t\tif(this.inputs)\r\n\t\t\tfor(var i = 0; i < this.inputs.length; ++i)\r\n\t\t\t{\r\n\t\t\t\tvar origin_node = this.getInputNode(i);\r\n\t\t\t\tif(origin_node)\r\n\t\t\t\t\torigin_node.propagateDestination( dest_name );\r\n\t\t\t}\r\n\t\t}\r\n\t\tif(!node_ctor.prototype.onPropertyChanged)\r\n\t\t\tnode_ctor.prototype.onPropertyChanged = function()\r\n\t\t\t{\r\n\t\t\t\tif(this.graph)\r\n\t\t\t\t\t this.graph._version++;\r\n\t\t\t}\r\n\r\n\t\t/*\r\n\t\tif(!node_ctor.prototype.onGetCode)\r\n\t\t\tnode_ctor.prototype.onGetCode = function()\r\n\t\t\t{\r\n\t\t\t\t//check destination to avoid lonely nodes\r\n\t\t\t\tif(!this.shader_destination)\r\n\t\t\t\t\treturn;\r\n\t\t\t\t//grab inputs with types\r\n\t\t\t\tvar inputs = [];\r\n\t\t\t\tif(this.inputs)\r\n\t\t\t\tfor(var i = 0; i < this.inputs.length; ++i)\r\n\t\t\t\t\tinputs.push({ type: this.getInputData(i), name: getInputLinkID(this,i) });\r\n\t\t\t\tvar outputs = [];\r\n\t\t\t\tif(this.outputs)\r\n\t\t\t\tfor(var i = 0; i < this.outputs.length; ++i)\r\n\t\t\t\t\toutputs.push({ name: getOutputLinkID(this,i) });\r\n\t\t\t\t//pass to code func\r\n\t\t\t\tvar results = this.extractCode(inputs);\r\n\t\t\t\t//grab output, pass to next\r\n\t\t\t\tif(results)\r\n\t\t\t\tfor(var i = 0; i < results.length; ++i)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar r = results[i];\r\n\t\t\t\t\tif(!r)\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\tthis.setOutputData(i,r.value);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t*/\r\n\r\n\t\tLiteGraph.registerNodeType( \"shader::\" + type, node_ctor );\r\n\t}\r\n\r\n\tfunction getShaderNodeVarName( node, name )\r\n\t{\r\n\t\treturn \"VAR_\" + (name || \"TEMP\") + \"_\" + node.id;\r\n\t}\r\n\r\n\tfunction getInputLinkID( node, slot )\r\n\t{\r\n\t\tif(!node.inputs)\r\n\t\t\treturn null;\r\n\t\tvar link = node.getInputLink( slot );\r\n\t\tif( !link )\r\n\t\t\treturn null;\r\n\t\tvar origin_node = node.graph.getNodeById( link.origin_id );\r\n\t\tif( !origin_node )\r\n\t\t\treturn null;\r\n\t\tif(origin_node.getOutputVarName)\r\n\t\t\treturn origin_node.getOutputVarName(link.origin_slot);\r\n\t\t//generate\r\n\t\treturn \"link_\" + origin_node.id + \"_\" + link.origin_slot;\r\n\t}\r\n\r\n\tfunction getOutputLinkID( node, slot )\r\n\t{\r\n\t\tif (!node.isOutputConnected(slot))\r\n\t\t\treturn null;\r\n\t\treturn \"link_\" + node.id + \"_\" + slot;\r\n\t}\r\n\r\n\tLGShaders.registerShaderNode = registerShaderNode;\r\n\tLGShaders.getInputLinkID = getInputLinkID;\r\n\tLGShaders.getOutputLinkID = getOutputLinkID;\r\n\tLGShaders.getShaderNodeVarName = getShaderNodeVarName;\r\n\tLGShaders.parseGLSLDescriptions = parseGLSLDescriptions;\r\n\r\n\t//given a const number, it transform it to a string that matches a type\r\n\tvar valueToGLSL = LiteGraph.valueToGLSL = function valueToGLSL( v, type, precision )\r\n\t{\r\n\t\tvar n = 5; //num decimals\r\n\t\tif(precision != null)\r\n\t\t\tn = precision;\r\n\t\tif(!type)\r\n\t\t{\r\n\t\t\tif(v.constructor === Number)\r\n\t\t\t\ttype = \"float\";\r\n\t\t\telse if(v.length)\r\n\t\t\t{\r\n\t\t\t\tswitch(v.length)\r\n\t\t\t\t{\r\n\t\t\t\t\tcase 2: type = \"vec2\"; break;\r\n\t\t\t\t\tcase 3: type = \"vec3\"; break;\r\n\t\t\t\t\tcase 4: type = \"vec4\"; break;\r\n\t\t\t\t\tcase 9: type = \"mat3\"; break;\r\n\t\t\t\t\tcase 16: type = \"mat4\"; break;\r\n\t\t\t\t\tdefault:\r\n\t\t\t\t\t\tthrow(\"unknown type for glsl value size\");\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tthrow(\"unknown type for glsl value: \" + v.constructor);\r\n\t\t}\r\n\t\tswitch(type)\r\n\t\t{\r\n\t\t\tcase 'float': return v.toFixed(n); break;\r\n\t\t\tcase 'vec2': return \"vec2(\" + v[0].toFixed(n) + \",\" + v[1].toFixed(n) + \")\"; break;\r\n\t\t\tcase 'color3':\r\n\t\t\tcase 'vec3': return \"vec3(\" + v[0].toFixed(n) + \",\" + v[1].toFixed(n) + \",\" + v[2].toFixed(n) + \")\"; break;\r\n\t\t\tcase 'color4':\r\n\t\t\tcase 'vec4': return \"vec4(\" + v[0].toFixed(n) + \",\" + v[1].toFixed(n) + \",\" + v[2].toFixed(n) + \",\" + v[3].toFixed(n) + \")\"; break;\r\n\t\t\tcase 'mat3': return \"mat3(1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0)\"; break; //not fully supported yet\r\n\t\t\tcase 'mat4': return \"mat4(1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0)\"; break;//not fully supported yet\r\n\t\t\tdefault:\r\n\t\t\t\tthrow(\"unknown glsl type in valueToGLSL:\", type);\r\n\t\t}\r\n\r\n\t\treturn \"\";\r\n\t}\r\n\r\n\t//makes sure that a var is of a type, and if not, it converts it\r\n\tvar varToTypeGLSL = LiteGraph.varToTypeGLSL = function varToTypeGLSL( v, input_type, output_type )\r\n\t{\r\n\t\tif(input_type == output_type)\r\n\t\t\treturn v;\r\n\t\tif(v == null)\r\n\t\t\tswitch(output_type)\r\n\t\t\t{\r\n\t\t\t\tcase \"float\": return \"0.0\";\r\n\t\t\t\tcase \"vec2\":  return \"vec2(0.0)\";\r\n\t\t\t\tcase \"vec3\":  return \"vec3(0.0)\";\r\n\t\t\t\tcase \"vec4\":  return \"vec4(0.0,0.0,0.0,1.0)\";\r\n\t\t\t\tdefault: //null\r\n\t\t\t\t\treturn null;\r\n\t\t\t}\r\n\r\n\t\tif(!output_type)\r\n\t\t\tthrow(\"error: no output type specified\");\r\n\t\tif(output_type == \"float\")\r\n\t\t{\r\n\t\t\tswitch(input_type)\r\n\t\t\t{\r\n\t\t\t\t//case \"float\":\r\n\t\t\t\tcase \"vec2\":\r\n\t\t\t\tcase \"vec3\":\r\n\t\t\t\tcase \"vec4\":\r\n\t\t\t\t\treturn v + \".x\";\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tdefault: //null\r\n\t\t\t\t\treturn \"0.0\";\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if(output_type == \"vec2\")\r\n\t\t{\r\n\t\t\tswitch(input_type)\r\n\t\t\t{\r\n\t\t\t\tcase \"float\":\r\n\t\t\t\t\treturn \"vec2(\"+v+\")\";\r\n\t\t\t\t//case \"vec2\":\r\n\t\t\t\tcase \"vec3\":\r\n\t\t\t\tcase \"vec4\":\r\n\t\t\t\t\treturn v + \".xy\";\r\n\t\t\t\tdefault: //null\r\n\t\t\t\t\treturn \"vec2(0.0)\";\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if(output_type == \"vec3\")\r\n\t\t{\r\n\t\t\tswitch(input_type)\r\n\t\t\t{\r\n\t\t\t\tcase \"float\":\r\n\t\t\t\t\treturn \"vec3(\"+v+\")\";\r\n\t\t\t\tcase \"vec2\":\r\n\t\t\t\t\treturn \"vec3(\" + v + \",0.0)\";\r\n\t\t\t\t//case \"vec3\":\r\n\t\t\t\tcase \"vec4\":\r\n\t\t\t\t\treturn v + \".xyz\";\r\n\t\t\t\tdefault: //null\r\n\t\t\t\t\treturn \"vec3(0.0)\";\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if(output_type == \"vec4\")\r\n\t\t{\r\n\t\t\tswitch(input_type)\r\n\t\t\t{\r\n\t\t\t\tcase \"float\":\r\n\t\t\t\t\treturn \"vec4(\"+v+\")\";\r\n\t\t\t\tcase \"vec2\":\r\n\t\t\t\t\treturn \"vec4(\" + v + \",0.0,1.0)\";\r\n\t\t\t\tcase \"vec3\":\r\n\t\t\t\t\treturn \"vec4(\" + v + \",1.0)\";\r\n\t\t\t\tdefault: //null\r\n\t\t\t\t\treturn \"vec4(0.0,0.0,0.0,1.0)\";\r\n\t\t\t}\r\n\t\t}\r\n\t\tthrow(\"type cannot be converted\");\r\n\t}\r\n\r\n\r\n\t//used to plug incompatible stuff\r\n\tvar convertVarToGLSLType = LiteGraph.convertVarToGLSLType = function convertVarToGLSLType( varname, type, target_type )\r\n\t{\r\n\t\tif(type == target_type)\r\n\t\t\treturn varname;\r\n\t\tif(type == \"float\")\r\n\t\t\treturn target_type + \"(\" + varname + \")\";\r\n\t\tif(target_type == \"vec2\") //works for vec2,vec3 and vec4\r\n\t\t\treturn \"vec2(\" + varname + \".xy)\";\r\n\t\tif(target_type == \"vec3\") //works for vec2,vec3 and vec4\r\n\t\t{\r\n\t\t\tif(type == \"vec2\")\r\n\t\t\t\treturn \"vec3(\" + varname + \",0.0)\";\r\n\t\t\tif(type == \"vec4\")\r\n\t\t\t\treturn \"vec4(\" + varname + \".xyz)\";\r\n\t\t}\r\n\t\tif(target_type == \"vec4\")\r\n\t\t{\r\n\t\t\tif(type == \"vec2\")\r\n\t\t\t\treturn \"vec4(\" + varname + \",0.0,0.0)\";\r\n\t\t\tif(target_type == \"vec3\")\r\n\t\t\t\treturn \"vec4(\" + varname + \",1.0)\";\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\t//used to host a shader body **************************************\r\n\tfunction LGShaderContext()\r\n\t{\r\n\t\t//to store the code template\r\n\t\tthis.vs_template = \"\";\r\n\t\tthis.fs_template = \"\";\r\n\r\n\t\t//required so nodes now where to fetch the input data\r\n\t\tthis.buffer_names = {\r\n\t\t\tuvs: \"v_coord\"\r\n\t\t};\r\n\r\n\t\tthis.extra = {}; //to store custom info from the nodes (like if this shader supports a feature, etc)\r\n\r\n\t\tthis._functions = {};\r\n\t\tthis._uniforms = {};\r\n\t\tthis._codeparts = {};\r\n\t\tthis._uniform_value = null;\r\n\t}\r\n\r\n\tLGShaderContext.prototype.clear = function()\r\n\t{\r\n\t\tthis._uniforms = {};\r\n\t\tthis._functions = {};\r\n\t\tthis._codeparts = {};\r\n\t\tthis._uniform_value = null;\r\n\r\n\t\tthis.extra = {};\r\n\t}\r\n\r\n\tLGShaderContext.prototype.addUniform = function( name, type, value )\r\n\t{\r\n\t\tthis._uniforms[ name ] = type;\r\n\t\tif(value != null)\r\n\t\t{\r\n\t\t\tif(!this._uniform_value)\r\n\t\t\t\tthis._uniform_value = {};\r\n\t\t\tthis._uniform_value[name] = value;\r\n\t\t}\r\n\t}\r\n\r\n\tLGShaderContext.prototype.addFunction = function( name, code )\r\n\t{\r\n\t\tthis._functions[name] = code;\r\n\t}\r\n\r\n\tLGShaderContext.prototype.addCode = function( hook, code, destinations )\r\n\t{\r\n\t\tdestinations = destinations || {\"\":\"\"};\r\n\t\tfor(var i in destinations)\r\n\t\t{\r\n\t\t\tvar h = i ? i + \"_\" + hook : hook;\r\n\t\t\tif(!this._codeparts[ h ])\r\n\t\t\t\tthis._codeparts[ h ] = code + \"\\n\";\r\n\t\t\telse\r\n\t\t\t\tthis._codeparts[ h ] += code + \"\\n\";\r\n\t\t}\r\n\t}\r\n\r\n\t//the system works by grabbing code fragments from every node and concatenating them in blocks depending on where must they be attached\r\n\tLGShaderContext.prototype.computeCodeBlocks = function( graph, extra_uniforms )\r\n\t{\r\n\t\t//prepare context\r\n\t\tthis.clear();\r\n\r\n\t\t//grab output nodes\r\n\t\tvar vertexout = graph.findNodesByType(\"shader::output/vertex\");\r\n\t\tvertexout = vertexout && vertexout.length ? vertexout[0] : null;\r\n\t\tvar fragmentout = graph.findNodesByType(\"shader::output/fragcolor\");\r\n\t\tfragmentout = fragmentout && fragmentout.length ? fragmentout[0] : null;\r\n\t\tif(!fragmentout) //??\r\n\t\t\treturn null; \r\n\r\n\t\t//propagate back destinations\r\n\t\tgraph.sendEventToAllNodes( \"clearDestination\" );\r\n\t\tif(vertexout)\r\n\t\t\tvertexout.propagateDestination(\"vs\");\r\n\t\tif(fragmentout)\r\n\t\t\tfragmentout.propagateDestination(\"fs\");\r\n\r\n\t\t//gets code from graph\r\n\t\tgraph.sendEventToAllNodes(\"onGetCode\", this );\r\n\r\n\t\tvar uniforms = \"\";\r\n\t\tfor(var i in this._uniforms)\r\n\t\t\tuniforms += \"uniform \" + this._uniforms[i] + \" \" + i + \";\\n\";\r\n\t\tif(extra_uniforms)\r\n\t\t\tfor(var i in extra_uniforms)\r\n\t\t\t\tuniforms += \"uniform \" + extra_uniforms[i] + \" \" + i + \";\\n\";\r\n\r\n\t\tvar functions = \"\";\r\n\t\tfor(var i in this._functions)\r\n\t\t\tfunctions += \"//\" + i + \"\\n\" + this._functions[i] + \"\\n\";\r\n\r\n\t\tvar blocks = this._codeparts;\r\n\t\tblocks.uniforms = uniforms;\r\n\t\tblocks.functions = functions;\r\n\t\treturn blocks;\r\n\t}\r\n\r\n\t//replaces blocks using the vs and fs template and returns the final codes\r\n\tLGShaderContext.prototype.computeShaderCode = function( graph )\r\n\t{\r\n\t\tvar blocks = this.computeCodeBlocks( graph );\r\n\t\tvar vs_code = GL.Shader.replaceCodeUsingContext( this.vs_template, blocks );\r\n\t\tvar fs_code = GL.Shader.replaceCodeUsingContext( this.fs_template, blocks );\r\n\t\treturn {\r\n\t\t\tvs_code: vs_code,\r\n\t\t\tfs_code: fs_code\r\n\t\t};\r\n\t}\r\n\r\n\t//generates the shader code from the template and the \r\n\tLGShaderContext.prototype.computeShader = function( graph, shader )\r\n\t{\r\n\t\tvar finalcode = this.computeShaderCode( graph );\r\n\t\tconsole.log( finalcode.vs_code, finalcode.fs_code );\r\n\r\n\t\tif(!LiteGraph.catch_exceptions)\r\n\t\t{\r\n\t\t\tthis._shader_error = true;\r\n\t\t\tif(shader)\r\n\t\t\t\tshader.updateShader( finalcode.vs_code, finalcode.fs_code );\r\n\t\t\telse\r\n\t\t\t\tshader = new GL.Shader( finalcode.vs_code, finalcode.fs_code );\r\n\t\t\tthis._shader_error = false;\r\n\t\t\treturn shader;\r\n\t\t}\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif(shader)\r\n\t\t\t\tshader.updateShader( finalcode.vs_code, finalcode.fs_code );\r\n\t\t\telse\r\n\t\t\t\tshader = new GL.Shader( finalcode.vs_code, finalcode.fs_code );\r\n\t\t\tthis._shader_error = false;\r\n\t\t\treturn shader;\r\n\t\t}\r\n\t\tcatch (err)\r\n\t\t{\r\n\t\t\tif(!this._shader_error)\r\n\t\t\t{\r\n\t\t\t\tconsole.error(err);\r\n\t\t\t\tif(err.indexOf(\"Fragment shader\") != -1)\r\n\t\t\t\t\tconsole.log( finalcode.fs_code.split(\"\\n\").map(function(v,i){ return i + \".- \" + v; }).join(\"\\n\") );\r\n\t\t\t\telse\r\n\t\t\t\t\tconsole.log( finalcode.vs_code );\r\n\t\t\t}\r\n\t\t\tthis._shader_error = true;\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\treturn null;//never here\r\n\t}\r\n\r\n\tLGShaderContext.prototype.getShader = function( graph )\r\n\t{\r\n\t\t//if graph not changed?\r\n\t\tif(this._shader && this._shader._version == graph._version)\r\n\t\t\treturn this._shader;\r\n\r\n\t\t//compile shader\r\n\t\tvar shader = this.computeShader( graph, this._shader );\r\n\t\tif(!shader)\r\n\t\t\treturn null;\r\n\t\t\r\n\t\tthis._shader = shader;\r\n\t\tshader._version = graph._version;\r\n\t\treturn shader;\r\n\t}\r\n\r\n\t//some shader nodes could require to fill the box with some uniforms\r\n\tLGShaderContext.prototype.fillUniforms = function( uniforms, param )\r\n\t{\r\n\t\tif(!this._uniform_value)\r\n\t\t\treturn;\r\n\r\n\t\tfor(var i in this._uniform_value)\r\n\t\t{\r\n\t\t\tvar v = this._uniform_value[i];\r\n\t\t\tif(v == null)\r\n\t\t\t\tcontinue;\r\n\t\t\tif(v.constructor === Function)\r\n\t\t\t\tuniforms[i] = v.call( this, param );\r\n\t\t\telse if(v.constructor === GL.Texture)\r\n\t\t\t{\r\n\t\t\t\t//todo...\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tuniforms[i] = v;\r\n\t\t}\r\n\t}\r\n\r\n\tLiteGraph.ShaderContext = LiteGraph.Shaders.Context = LGShaderContext;\r\n\r\n\t// LGraphShaderGraph *****************************\r\n\t// applies a shader graph to texture, it can be uses as an example\r\n\r\n\tfunction LGraphShaderGraph() {\r\n\r\n\t\t//before inputs\r\n        this.subgraph = new LiteGraph.LGraph();\r\n        this.subgraph._subgraph_node = this;\r\n        this.subgraph._is_subgraph = true;\r\n\t\tthis.subgraph.filter = \"shader\";\r\n\r\n\t\tthis.addInput(\"in\", \"texture\");\r\n\t\tthis.addOutput(\"out\", \"texture\");\r\n\t\tthis.properties = { width: 0, height: 0, alpha: false, precision: typeof(LGraphTexture) != \"undefined\" ? LGraphTexture.DEFAULT : 2 };\r\n\r\n\t\tvar inputNode = this.subgraph.findNodesByType(\"shader::input/uniform\")[0];\r\n\t\tinputNode.pos = [200,300];\r\n\r\n\t\tvar sampler = LiteGraph.createNode(\"shader::texture/sampler2D\");\r\n\t\tsampler.pos = [400,300];\r\n\t\tthis.subgraph.add( sampler );\r\n\r\n\t\tvar outnode = LiteGraph.createNode(\"shader::output/fragcolor\");\r\n\t\toutnode.pos = [600,300];\r\n\t\tthis.subgraph.add( outnode );\r\n\r\n\t\tinputNode.connect( 0, sampler );\r\n\t\tsampler.connect( 0, outnode );\r\n\r\n\t\tthis.size = [180,60];\r\n\t\tthis.redraw_on_mouse = true; //force redraw\r\n\r\n\t\tthis._uniforms = {};\r\n\t\tthis._shader = null;\r\n\t\tthis._context = new LGShaderContext();\r\n\t\tthis._context.vs_template = \"#define VERTEX\\n\" + GL.Shader.SCREEN_VERTEX_SHADER;\r\n\t\tthis._context.fs_template = LGraphShaderGraph.template;\r\n\t}\r\n\r\n\tLGraphShaderGraph.template = \"\\n\\\r\n#define FRAGMENT\\n\\\r\nprecision highp float;\\n\\\r\nvarying vec2 v_coord;\\n\\\r\n{{varying}}\\n\\\r\n{{uniforms}}\\n\\\r\n{{functions}}\\n\\\r\n{{fs_functions}}\\n\\\r\nvoid main() {\\n\\n\\\r\nvec2 uv = v_coord;\\n\\\r\nvec4 fragcolor = vec4(0.0);\\n\\\r\nvec4 fragcolor1 = vec4(0.0);\\n\\\r\n{{fs_code}}\\n\\\r\ngl_FragColor = fragcolor;\\n\\\r\n}\\n\\\r\n\t\";\r\n\r\n\tLGraphShaderGraph.widgets_info = {\r\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\r\n\t};\r\n\r\n\tLGraphShaderGraph.title = \"ShaderGraph\";\r\n\tLGraphShaderGraph.desc = \"Builds a shader using a graph\";\r\n\tLGraphShaderGraph.input_node_type = \"input/uniform\";\r\n\tLGraphShaderGraph.output_node_type = \"output/fragcolor\";\r\n\tLGraphShaderGraph.title_color = SHADERNODES_COLOR;\r\n\r\n\tLGraphShaderGraph.prototype.onSerialize = function(o)\r\n\t{\r\n\t\to.subgraph = this.subgraph.serialize();\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.onConfigure = function(o)\r\n\t{\r\n\t\tthis.subgraph.configure(o.subgraph);\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.onExecute = function() {\r\n\t\tif (!this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\t//read input texture\r\n\t\tvar intex = this.getInputData(0);\r\n\t\tif(intex && intex.constructor != GL.Texture)\r\n\t\t\tintex = null;\r\n\r\n\t\tvar w = this.properties.width | 0;\r\n\t\tvar h = this.properties.height | 0;\r\n\t\tif (w == 0) {\r\n\t\t\tw = intex ? intex.width : gl.viewport_data[2];\r\n\t\t} //0 means default\r\n\t\tif (h == 0) {\r\n\t\t\th = intex ? intex.height : gl.viewport_data[3];\r\n\t\t} //0 means default\r\n\r\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision, intex );\r\n\r\n\t\tvar texture = this._texture;\r\n\t\tif ( !texture || texture.width != w || texture.height != h || texture.type != type ) {\r\n\t\t\ttexture = this._texture = new GL.Texture(w, h, {\r\n\t\t\t\ttype: type,\r\n\t\t\t\tformat: this.alpha ? gl.RGBA : gl.RGB,\r\n\t\t\t\tfilter: gl.LINEAR\r\n\t\t\t});\r\n\t\t}\r\n\t\t\r\n\t\tvar shader = this.getShader( this.subgraph );\r\n\t\tif(!shader)\r\n\t\t\treturn;\r\n\r\n\t\tvar uniforms = this._uniforms;\r\n\t\tthis._context.fillUniforms( uniforms );\r\n\r\n\t\tvar tex_slot = 0;\r\n\t\tif(this.inputs)\r\n\t\tfor(var i = 0; i < this.inputs.length; ++i)\r\n\t\t{\r\n\t\t\tvar input = this.inputs[i];\r\n\t\t\tvar data = this.getInputData(i);\r\n\t\t\tif(input.type == \"texture\")\r\n\t\t\t{\r\n\t\t\t\tif(!data)\r\n\t\t\t\t\tdata = GL.Texture.getWhiteTexture();\r\n\t\t\t\tdata = data.bind(tex_slot++);\r\n\t\t\t}\r\n\r\n\t\t\tif(data != null)\r\n\t\t\t\tuniforms[ \"u_\" + input.name ] = data;\r\n\t\t}\r\n\r\n\t\tvar mesh = GL.Mesh.getScreenQuad();\r\n\r\n\t\tgl.disable( gl.DEPTH_TEST );\r\n\t\tgl.disable( gl.BLEND );\r\n\r\n\t\ttexture.drawTo(function(){\r\n\t\t\tshader.uniforms( uniforms );\r\n\t\t\tshader.draw( mesh );\r\n\t\t});\r\n\r\n\t\t//use subgraph output \r\n\t\tthis.setOutputData(0, texture );\r\n\t};\r\n\r\n\t//add input node inside subgraph\r\n\tLGraphShaderGraph.prototype.onInputAdded = function( slot_info )\r\n\t{\r\n\t\tvar subnode = LiteGraph.createNode(\"shader::input/uniform\");\r\n\t\tsubnode.setProperty(\"name\",slot_info.name);\r\n\t\tsubnode.setProperty(\"type\",slot_info.type);\r\n\t\tthis.subgraph.add( subnode );\r\n\t}\r\n\r\n\t//remove all\r\n\tLGraphShaderGraph.prototype.onInputRemoved = function( slot, slot_info )\r\n\t{\r\n\t\tvar nodes = this.subgraph.findNodesByType(\"shader::input/uniform\");\r\n\t\tfor(var i = 0; i < nodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar node = nodes[i];\r\n\t\t\tif(node.properties.name == slot_info.name )\r\n\t\t\t\tthis.subgraph.remove( node );\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.computeSize = function()\r\n\t{\r\n\t\tvar num_inputs = this.inputs ? this.inputs.length : 0;\r\n\t\tvar num_outputs = this.outputs ? this.outputs.length : 0;\r\n\t\treturn [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT + 10];\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.getShader = function()\r\n\t{\r\n\t\tvar shader = this._context.getShader( this.subgraph );\r\n\t\tif(!shader)\r\n\t\t\tthis.boxcolor = \"red\";\r\n\t\telse\r\n\t\t\tthis.boxcolor = null;\r\n\t\treturn shader;\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.onDrawBackground = function(ctx, graphcanvas, canvas, pos)\r\n\t{\r\n\t\tif(this.flags.collapsed)\r\n\t\t\treturn;\r\n\r\n\t\t//allows to preview the node if the canvas is a webgl canvas\r\n\t\tvar tex = this.getOutputData(0);\r\n\t\tvar inputs_y = this.inputs ? this.inputs.length * LiteGraph.NODE_SLOT_HEIGHT : 0;\r\n\t\tif (tex && ctx == tex.gl && this.size[1] > inputs_y + LiteGraph.NODE_TITLE_HEIGHT ) {\r\n\t\t\tctx.drawImage( tex, 10,y, this.size[0] - 20, this.size[1] - inputs_y - LiteGraph.NODE_TITLE_HEIGHT );\r\n\t\t}\r\n\r\n\t\tvar y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n\r\n\t\t//button\r\n\t\tvar over = LiteGraph.isInsideRectangle(pos[0],pos[1],this.pos[0],this.pos[1] + y,this.size[0],LiteGraph.NODE_TITLE_HEIGHT);\r\n\t\tctx.fillStyle = over ? \"#555\" : \"#222\";\r\n\t\tctx.beginPath();\r\n\t\tif (this._shape == LiteGraph.BOX_SHAPE)\r\n\t\t\tctx.rect(0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT);\r\n\t\telse\r\n\t\t\tctx.roundRect( 0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT, 0, 8);\r\n\t\tctx.fill();\r\n\r\n\t\t//button\r\n\t\tctx.textAlign = \"center\";\r\n\t\tctx.font = \"24px Arial\";\r\n\t\tctx.fillStyle = over ? \"#DDD\" : \"#999\";\r\n\t\tctx.fillText( \"+\", this.size[0] * 0.5, y + 24 );\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.onMouseDown = function(e, localpos, graphcanvas)\r\n\t{\r\n\t\tvar y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;\r\n\t\tif(localpos[1] > y)\r\n\t\t{\r\n\t\t\tgraphcanvas.showSubgraphPropertiesDialog(this);\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.onDrawSubgraphBackground = function(graphcanvas)\r\n\t{\r\n\t\t//TODO\r\n\t}\r\n\r\n\tLGraphShaderGraph.prototype.getExtraMenuOptions = function(graphcanvas)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tvar options = [{ content: \"Print Code\", callback: function(){\r\n\t\t\tvar code = that._context.computeShaderCode();\r\n\t\t\tconsole.log( code.vs_code, code.fs_code );\r\n\t\t}}];\r\n\r\n\t\treturn options;\r\n\t}\r\n\r\n\tLiteGraph.registerNodeType( \"texture/shaderGraph\", LGraphShaderGraph );\r\n\r\n\tfunction shaderNodeFromFunction( classname, params, return_type, code )\r\n\t{\r\n\t\t//TODO\r\n\t}\r\n\r\n\t//Shader Nodes ***********************************************************\r\n\r\n\t//applies a shader graph to a code\r\n\tfunction LGraphShaderUniform() {\r\n\t\tthis.addOutput(\"out\", \"\");\r\n\t\tthis.properties = { name: \"\", type: \"\" };\r\n\t}\r\n\r\n\tLGraphShaderUniform.title = \"Uniform\";\r\n\tLGraphShaderUniform.desc = \"Input data for the shader\";\r\n\r\n\tLGraphShaderUniform.prototype.getTitle = function()\r\n\t{\r\n\t\tif( this.properties.name && this.flags.collapsed)\r\n\t\t\treturn this.properties.type + \" \" + this.properties.name;\r\n\t\treturn \"Uniform\";\r\n\t}\r\n\r\n\tLGraphShaderUniform.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tthis.outputs[0].name = this.properties.type + \" \" + this.properties.name;\r\n\t}\r\n\r\n\tLGraphShaderUniform.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar type = this.properties.type;\r\n\t\tif( !type )\r\n\t\t{\r\n\t\t\tif( !context.onGetPropertyInfo )\r\n\t\t\t\treturn;\r\n\t\t\tvar info = context.onGetPropertyInfo( this.property.name );\r\n\t\t\tif(!info)\r\n\t\t\t\treturn;\r\n\t\t\ttype = info.type;\r\n\t\t}\r\n\t\tif(type == \"number\")\r\n\t\t\ttype = \"float\";\r\n\t\telse if(type == \"texture\")\r\n\t\t\ttype = \"sampler2D\";\r\n\t\tif ( LGShaders.GLSL_types.indexOf(type) == -1 )\r\n\t\t\treturn;\r\n\r\n\t\tcontext.addUniform( \"u_\" + this.properties.name, type );\r\n\t\tthis.setOutputData( 0, type );\r\n\t}\r\n\r\n\tLGraphShaderUniform.prototype.getOutputVarName = function(slot)\r\n\t{\r\n\t\treturn \"u_\" + this.properties.name;\r\n\t}\r\n\r\n\tregisterShaderNode( \"input/uniform\", LGraphShaderUniform );\r\n\r\n\r\n\tfunction LGraphShaderAttribute() {\r\n\t\tthis.addOutput(\"out\", \"vec2\");\r\n\t\tthis.properties = { name: \"coord\", type: \"vec2\" };\r\n\t}\r\n\r\n\tLGraphShaderAttribute.title = \"Attribute\";\r\n\tLGraphShaderAttribute.desc = \"Input data from mesh attribute\";\r\n\r\n\tLGraphShaderAttribute.prototype.getTitle = function()\r\n\t{\r\n\t\treturn \"att. \" + this.properties.name;\r\n\t}\r\n\r\n\tLGraphShaderAttribute.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar type = this.properties.type;\r\n\t\tif( !type || LGShaders.GLSL_types.indexOf(type) == -1 )\r\n\t\t\treturn;\r\n\t\tif(type == \"number\")\r\n\t\t\ttype = \"float\";\r\n\t\tif( this.properties.name != \"coord\")\r\n\t\t{\r\n\t\t\tcontext.addCode( \"varying\", \" varying \" + type +\" v_\" + this.properties.name + \";\" );\r\n\t\t\t//if( !context.varyings[ this.properties.name ] )\r\n\t\t\t//context.addCode( \"vs_code\", \"v_\" + this.properties.name + \" = \" + input_name + \";\" );\r\n\t\t}\r\n\t\tthis.setOutputData( 0, type );\r\n\t}\r\n\r\n\tLGraphShaderAttribute.prototype.getOutputVarName = function(slot)\r\n\t{\r\n\t\treturn \"v_\" + this.properties.name;\r\n\t}\r\n\r\n\tregisterShaderNode( \"input/attribute\", LGraphShaderAttribute );\r\n\r\n\tfunction LGraphShaderSampler2D() {\r\n\t\tthis.addInput(\"tex\", \"sampler2D\");\r\n\t\tthis.addInput(\"uv\", \"vec2\");\r\n\t\tthis.addOutput(\"rgba\", \"vec4\");\r\n\t\tthis.addOutput(\"rgb\", \"vec3\");\r\n\t}\r\n\r\n\tLGraphShaderSampler2D.title = \"Sampler2D\";\r\n\tLGraphShaderSampler2D.desc = \"Reads a pixel from a texture\";\r\n\r\n\tLGraphShaderSampler2D.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar texname = getInputLinkID( this, 0 );\r\n\t\tvar varname = getShaderNodeVarName(this);\r\n\t\tvar code = \"vec4 \" + varname + \" = vec4(0.0);\\n\";\r\n\t\tif(texname)\r\n\t\t{\r\n\t\t\tvar uvname = getInputLinkID( this, 1 ) || context.buffer_names.uvs;\r\n\t\t\tcode += varname + \" = texture2D(\"+texname+\",\"+uvname+\");\\n\";\r\n\t\t}\r\n\r\n\t\tvar link0 = getOutputLinkID( this, 0 );\r\n\t\tif(link0)\r\n\t\t\tcode += \"vec4 \" + getOutputLinkID( this, 0 ) + \" = \"+varname+\";\\n\";\r\n\r\n\t\tvar link1 = getOutputLinkID( this, 1 );\r\n\t\tif(link1)\r\n\t\t\tcode += \"vec3 \" + getOutputLinkID( this, 1 ) + \" = \"+varname+\".xyz;\\n\";\r\n\r\n\t\tcontext.addCode( \"code\", code, this.shader_destination );\r\n\t\tthis.setOutputData( 0, \"vec4\" );\r\n\t\tthis.setOutputData( 1, \"vec3\" );\r\n\t}\r\n\r\n\tregisterShaderNode( \"texture/sampler2D\", LGraphShaderSampler2D );\r\n\r\n\t//*********************************\r\n\r\n\tfunction LGraphShaderConstant()\r\n\t{\r\n\t\tthis.addOutput(\"\",\"float\");\r\n\r\n\t\tthis.properties = {\r\n\t\t\ttype: \"float\",\r\n\t\t\tvalue: 0\r\n\t\t};\r\n\r\n\t\tthis.addWidget(\"combo\",\"type\",\"float\",null, { values: GLSL_types_const, property: \"type\" } );\r\n\t\tthis.updateWidgets();\r\n\t}\r\n\r\n\tLGraphShaderConstant.title = \"const\";\r\n\r\n\tLGraphShaderConstant.prototype.getTitle = function()\r\n\t{\r\n\t\tif(this.flags.collapsed)\r\n\t\t\treturn valueToGLSL( this.properties.value, this.properties.type, 2 );\r\n\t\treturn \"Const\";\r\n\t}\r\n\r\n\tLGraphShaderConstant.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tif(name == \"type\")\r\n\t\t{\r\n\t\t\tif(this.outputs[0].type != value)\r\n\t\t\t{\r\n\t\t\t\tthis.disconnectOutput(0);\r\n\t\t\t\tthis.outputs[0].type = value;\r\n\t\t\t}\r\n\t\t\tthis.widgets.length = 1; //remove extra widgets\r\n\t\t\tthis.updateWidgets();\r\n\t\t}\r\n\t\tif(name == \"value\")\r\n\t\t{\r\n\t\t\tif(!value.length)\r\n\t\t\t\tthis.widgets[1].value = value;\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.widgets[1].value = value[1];\r\n\t\t\t\tif(value.length > 2)\r\n\t\t\t\t\tthis.widgets[2].value = value[2];\r\n\t\t\t\tif(value.length > 3)\r\n\t\t\t\t\tthis.widgets[3].value = value[3];\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderConstant.prototype.updateWidgets = function( old_value )\r\n\t{\r\n\t\tvar that = this;\r\n\t\tvar old_value = this.properties.value;\r\n\t\tvar options = { step: 0.01 };\r\n\t\tswitch(this.properties.type)\r\n\t\t{\r\n\t\t\tcase 'float': \r\n\t\t\t\tthis.properties.value = 0;\r\n\t\t\t\tthis.addWidget(\"number\",\"v\",0,{ step:0.01, property: \"value\" });\r\n\t\t\t\tbreak;\r\n\t\t\tcase 'vec2': \r\n\t\t\t\tthis.properties.value = old_value && old_value.length == 2 ? [old_value[0],old_value[1]] : [0,0,0];\r\n\t\t\t\tthis.addWidget(\"number\",\"x\",this.properties.value[0], function(v){ that.properties.value[0] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"y\",this.properties.value[1], function(v){ that.properties.value[1] = v; },options); \r\n\t\t\t\tbreak;\r\n\t\t\tcase 'vec3': \r\n\t\t\t\tthis.properties.value = old_value && old_value.length == 3 ? [old_value[0],old_value[1],old_value[2]] : [0,0,0];\r\n\t\t\t\tthis.addWidget(\"number\",\"x\",this.properties.value[0], function(v){ that.properties.value[0] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"y\",this.properties.value[1], function(v){ that.properties.value[1] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"z\",this.properties.value[2], function(v){ that.properties.value[2] = v; },options); \r\n\t\t\t\tbreak;\r\n\t\t\tcase 'vec4': \r\n\t\t\t\tthis.properties.value = old_value && old_value.length == 4 ? [old_value[0],old_value[1],old_value[2],old_value[3]] : [0,0,0,0];\r\n\t\t\t\tthis.addWidget(\"number\",\"x\",this.properties.value[0], function(v){ that.properties.value[0] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"y\",this.properties.value[1], function(v){ that.properties.value[1] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"z\",this.properties.value[2], function(v){ that.properties.value[2] = v; },options); \r\n\t\t\t\tthis.addWidget(\"number\",\"w\",this.properties.value[3], function(v){ that.properties.value[3] = v; },options); \r\n\t\t\t\tbreak;\r\n\t\t\tdefault:\r\n\t\t\t\tconsole.error(\"unknown type for constant\");\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderConstant.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar value = valueToGLSL( this.properties.value, this.properties.type );\r\n\t\tvar link_name = getOutputLinkID(this,0);\r\n\t\tif(!link_name) //not connected\r\n\t\t\treturn;\r\n\r\n\t\tvar code = \"\t\" + this.properties.type + \" \" + link_name + \" = \" + value + \";\";\r\n\t\tcontext.addCode( \"code\", code, this.shader_destination );\r\n\r\n\t\tthis.setOutputData( 0, this.properties.type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"const/const\", LGraphShaderConstant );\r\n\r\n\tfunction LGraphShaderVec2()\r\n\t{\r\n\t\tthis.addInput(\"xy\",\"vec2\");\r\n\t\tthis.addInput(\"x\",\"float\");\r\n\t\tthis.addInput(\"y\",\"float\");\r\n\t\tthis.addOutput(\"xy\",\"vec2\");\r\n\t\tthis.addOutput(\"x\",\"float\");\r\n\t\tthis.addOutput(\"y\",\"float\");\r\n\r\n\t\tthis.properties = { x: 0, y: 0 };\r\n\t}\r\n\r\n\tLGraphShaderVec2.title = \"vec2\";\r\n\tLGraphShaderVec2.varmodes = [\"xy\",\"x\",\"y\"];\r\n\r\n\tLGraphShaderVec2.prototype.onPropertyChanged = function()\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\t this.graph._version++;\r\n\t}\r\n\r\n\tLGraphShaderVec2.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar props = this.properties;\r\n\r\n\t\tvar varname = getShaderNodeVarName(this);\r\n\t\tvar code = \"\tvec2 \" + varname + \" = \" + valueToGLSL([props.x,props.y]) + \";\\n\";\r\n\r\n\t\tfor(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec2.varmodes[i];\r\n\t\t\tvar inlink = getInputLinkID(this,i);\r\n\t\t\tif(!inlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tcode += \"\t\" + varname + \".\"+varmode+\" = \" + inlink + \";\\n\";\r\n\t\t}\r\n\r\n\t\tfor(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec2.varmodes[i];\r\n\t\t\tvar outlink = getOutputLinkID(this,i);\r\n\t\t\tif(!outlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar type = GLSL_types_const[varmode.length - 1];\r\n\t\t\tcode += \"\t\"+type+\" \" + outlink + \" = \" + varname + \".\" + varmode + \";\\n\";\r\n\t\t\tthis.setOutputData( i, type );\r\n\t\t}\r\n\r\n\t\tcontext.addCode( \"code\", code, this.shader_destination );\r\n\t}\r\n\r\n\tregisterShaderNode( \"const/vec2\", LGraphShaderVec2 );\t\r\n\r\n\tfunction LGraphShaderVec3()\r\n\t{\r\n\t\tthis.addInput(\"xyz\",\"vec3\");\r\n\t\tthis.addInput(\"x\",\"float\");\r\n\t\tthis.addInput(\"y\",\"float\");\r\n\t\tthis.addInput(\"z\",\"float\");\r\n\t\tthis.addInput(\"xy\",\"vec2\");\r\n\t\tthis.addInput(\"xz\",\"vec2\");\r\n\t\tthis.addInput(\"yz\",\"vec2\");\r\n\t\tthis.addOutput(\"xyz\",\"vec3\");\r\n\t\tthis.addOutput(\"x\",\"float\");\r\n\t\tthis.addOutput(\"y\",\"float\");\r\n\t\tthis.addOutput(\"z\",\"float\");\r\n\t\tthis.addOutput(\"xy\",\"vec2\");\r\n\t\tthis.addOutput(\"xz\",\"vec2\");\r\n\t\tthis.addOutput(\"yz\",\"vec2\");\r\n\r\n\t\tthis.properties = { x:0, y: 0, z: 0 };\r\n\t}\r\n\r\n\tLGraphShaderVec3.title = \"vec3\";\r\n\tLGraphShaderVec3.varmodes = [\"xyz\",\"x\",\"y\",\"z\",\"xy\",\"xz\",\"yz\"];\r\n\r\n\tLGraphShaderVec3.prototype.onPropertyChanged = function()\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\tthis.graph._version++;\r\n\t}\r\n\r\n\tLGraphShaderVec3.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar props = this.properties;\r\n\r\n\t\tvar varname = getShaderNodeVarName(this);\r\n\t\tvar code = \"vec3 \" + varname + \" = \" + valueToGLSL([props.x,props.y,props.z]) + \";\\n\";\r\n\r\n\t\tfor(var i = 0;i < LGraphShaderVec3.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec3.varmodes[i];\r\n\t\t\tvar inlink = getInputLinkID(this,i);\r\n\t\t\tif(!inlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tcode += \"\t\" + varname + \".\"+varmode+\" = \" + inlink + \";\\n\";\r\n\t\t}\r\n\r\n\t\tfor(var i = 0; i < LGraphShaderVec3.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec3.varmodes[i];\r\n\t\t\tvar outlink = getOutputLinkID(this,i);\r\n\t\t\tif(!outlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar type = GLSL_types_const[varmode.length - 1];\r\n\t\t\tcode += \"\t\"+type+\" \" + outlink + \" = \" + varname + \".\" + varmode + \";\\n\";\r\n\t\t\tthis.setOutputData( i, type );\r\n\t\t}\r\n\r\n\t\tcontext.addCode( \"code\", code, this.shader_destination );\r\n\t}\r\n\r\n\tregisterShaderNode( \"const/vec3\", LGraphShaderVec3 );\t\r\n\r\n\r\n\tfunction LGraphShaderVec4()\r\n\t{\r\n\t\tthis.addInput(\"xyzw\",\"vec4\");\r\n\t\tthis.addInput(\"xyz\",\"vec3\");\r\n\t\tthis.addInput(\"x\",\"float\");\r\n\t\tthis.addInput(\"y\",\"float\");\r\n\t\tthis.addInput(\"z\",\"float\");\r\n\t\tthis.addInput(\"w\",\"float\");\r\n\t\tthis.addInput(\"xy\",\"vec2\");\r\n\t\tthis.addInput(\"yz\",\"vec2\");\r\n\t\tthis.addInput(\"zw\",\"vec2\");\r\n\t\tthis.addOutput(\"xyzw\",\"vec4\");\r\n\t\tthis.addOutput(\"xyz\",\"vec3\");\r\n\t\tthis.addOutput(\"x\",\"float\");\r\n\t\tthis.addOutput(\"y\",\"float\");\r\n\t\tthis.addOutput(\"z\",\"float\");\r\n\t\tthis.addOutput(\"xy\",\"vec2\");\r\n\t\tthis.addOutput(\"yz\",\"vec2\");\r\n\t\tthis.addOutput(\"zw\",\"vec2\");\r\n\r\n\t\tthis.properties = { x:0, y: 0, z: 0, w: 0 };\r\n\t}\r\n\r\n\tLGraphShaderVec4.title = \"vec4\";\r\n\tLGraphShaderVec4.varmodes = [\"xyzw\",\"xyz\",\"x\",\"y\",\"z\",\"w\",\"xy\",\"yz\",\"zw\"];\r\n\r\n\tLGraphShaderVec4.prototype.onPropertyChanged = function()\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\tthis.graph._version++;\r\n\t}\r\n\r\n\tLGraphShaderVec4.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tvar props = this.properties;\r\n\r\n\t\tvar varname = getShaderNodeVarName(this);\r\n\t\tvar code = \"vec4 \" + varname + \" = \" + valueToGLSL([props.x,props.y,props.z,props.w]) + \";\\n\";\r\n\r\n\t\tfor(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec4.varmodes[i];\r\n\t\t\tvar inlink = getInputLinkID(this,i);\r\n\t\t\tif(!inlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tcode += \"\t\" + varname + \".\"+varmode+\" = \" + inlink + \";\\n\";\r\n\t\t}\r\n\r\n\t\tfor(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i)\r\n\t\t{\r\n\t\t\tvar varmode = LGraphShaderVec4.varmodes[i];\r\n\t\t\tvar outlink = getOutputLinkID(this,i);\r\n\t\t\tif(!outlink)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar type = GLSL_types_const[varmode.length - 1];\r\n\t\t\tcode += \"\t\"+type+\" \" + outlink + \" = \" + varname + \".\" + varmode + \";\\n\";\r\n\t\t\tthis.setOutputData( i, type );\r\n\t\t}\r\n\r\n\t\tcontext.addCode( \"code\", code, this.shader_destination );\r\n\r\n\t}\r\n\r\n\tregisterShaderNode( \"const/vec4\", LGraphShaderVec4 );\t\r\n\t\r\n\t//*********************************\r\n\r\n\tfunction LGraphShaderFragColor() {\r\n\t\tthis.addInput(\"color\", LGShaders.ALL_TYPES );\r\n\t\tthis.block_delete = true;\r\n\t}\r\n\r\n\tLGraphShaderFragColor.title = \"FragColor\";\r\n\tLGraphShaderFragColor.desc = \"Pixel final color\";\r\n\r\n\tLGraphShaderFragColor.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tvar link_name = getInputLinkID( this, 0 );\r\n\t\tif(!link_name)\r\n\t\t\treturn;\r\n\t\tvar type = this.getInputData(0);\r\n\t\tvar code = varToTypeGLSL( link_name, type, \"vec4\" );\r\n\t\tcontext.addCode(\"fs_code\", \"fragcolor = \" + code + \";\");\r\n\t}\r\n\r\n\tregisterShaderNode( \"output/fragcolor\", LGraphShaderFragColor );\r\n\r\n\r\n\t/*\r\n\tfunction LGraphShaderDiscard()\r\n\t{\r\n\t\tthis.addInput(\"v\",\"T\");\r\n\t\tthis.addInput(\"min\",\"T\");\r\n\t\tthis.properties = { min_value: 0.0 };\r\n\t\tthis.addWidget(\"number\",\"min\",0,{ step: 0.01, property: \"min_value\" });\r\n\t}\r\n\r\n\tLGraphShaderDiscard.title = \"Discard\";\r\n\r\n\tLGraphShaderDiscard.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlink = getInputLinkID(this,0);\r\n\t\tvar inlink1 = getInputLinkID(this,1);\r\n\r\n\t\tif(!inlink && !inlink1) //not connected\r\n\t\t\treturn;\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = ( (\" + inlink + \" - \"+minv+\") / (\"+ maxv+\" - \"+minv+\") ) * (\"+ maxv2+\" - \"+minv2+\") + \" + minv2 + \";\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"output/discard\", LGraphShaderDiscard );\r\n\t*/\r\n\r\n\r\n\t// *************************************************\r\n\r\n\tfunction LGraphShaderOperation()\r\n\t{\r\n\t\tthis.addInput(\"A\", LGShaders.ALL_TYPES );\r\n\t\tthis.addInput(\"B\", LGShaders.ALL_TYPES );\r\n\t\tthis.addOutput(\"out\",\"\");\r\n\t\tthis.properties = {\r\n\t\t\toperation: \"*\"\r\n\t\t};\r\n\t\tthis.addWidget(\"combo\",\"op.\",this.properties.operation,{ property: \"operation\", values: LGraphShaderOperation.operations });\r\n\t}\r\n\r\n\tLGraphShaderOperation.title = \"Operation\";\r\n\tLGraphShaderOperation.operations = [\"+\",\"-\",\"*\",\"/\"];\r\n\r\n\tLGraphShaderOperation.prototype.getTitle = function()\r\n\t{\r\n\t\tif(this.flags.collapsed)\r\n\t\t\treturn \"A\" + this.properties.operation + \"B\";\r\n\t\telse\r\n\t\t\treturn \"Operation\";\r\n\t}\r\n\r\n\tLGraphShaderOperation.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tif(!this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlinks = [];\r\n\t\tfor(var i = 0; i < 3; ++i)\r\n\t\t\tinlinks.push( { name: getInputLinkID(this,i), type: this.getInputData(i) || \"float\" } );\r\n\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\t\tif(!outlink) //not connected\r\n\t\t\treturn;\r\n\r\n\t\t//func_desc\r\n\t\tvar base_type = inlinks[0].type;\r\n\t\tvar return_type = base_type;\r\n\t\tvar op = this.properties.operation;\r\n\r\n\t\tvar params = [];\r\n\t\tfor(var i = 0; i < 2; ++i)\r\n\t\t{\r\n\t\t\tvar param_code = inlinks[i].name;\r\n\t\t\tif(param_code == null) //not plugged\r\n\t\t\t{\r\n\t\t\t\tparam_code = p.value != null ? p.value : \"(1.0)\";\r\n\t\t\t\tinlinks[i].type = \"float\";\r\n\t\t\t}\r\n\r\n\t\t\t//convert\r\n\t\t\tif( inlinks[i].type != base_type ) \r\n\t\t\t{\r\n\t\t\t\tif( inlinks[i].type == \"float\" && (op == \"*\" || op == \"/\") )\r\n\t\t\t\t{\r\n\t\t\t\t\t//I find hard to create the opposite condition now, so I prefeer an else\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t\tparam_code = convertVarToGLSLType( param_code, inlinks[i].type, base_type );\r\n\t\t\t}\r\n\t\t\tparams.push( param_code );\r\n\t\t}\r\n\t\t\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = \"+ params[0] + op + params[1] + \";\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"math/operation\", LGraphShaderOperation );\r\n\r\n\r\n\tfunction LGraphShaderFunc()\r\n\t{\r\n\t\tthis.addInput(\"A\", LGShaders.ALL_TYPES );\r\n\t\tthis.addInput(\"B\", LGShaders.ALL_TYPES );\r\n\t\tthis.addOutput(\"out\",\"\");\r\n\t\tthis.properties = {\r\n\t\t\tfunc: \"floor\"\r\n\t\t};\r\n\t\tthis._current = \"floor\";\r\n\t\tthis.addWidget(\"combo\",\"func\",this.properties.func,{ property: \"func\", values: GLSL_functions_name });\r\n\t}\r\n\r\n\tLGraphShaderFunc.title = \"Func\";\r\n\r\n\tLGraphShaderFunc.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\tthis.graph._version++;\r\n\r\n\t\tif(name == \"func\")\r\n\t\t{\r\n\t\t\tvar func_desc = GLSL_functions[ value ];\r\n\t\t\tif(!func_desc)\r\n\t\t\t\treturn;\r\n\r\n\t\t\t//remove extra inputs\r\n\t\t\tfor(var i = func_desc.params.length; i < this.inputs.length; ++i)\r\n\t\t\t\tthis.removeInput(i);\r\n\r\n\t\t\t//add and update inputs\r\n\t\t\tfor(var i = 0; i < func_desc.params.length; ++i)\r\n\t\t\t{\r\n\t\t\t\tvar p = func_desc.params[i];\r\n\t\t\t\tif( this.inputs[i] )\r\n\t\t\t\t\tthis.inputs[i].name = p.name + (p.value ? \" (\" + p.value + \")\" : \"\");\r\n\t\t\t\telse\r\n\t\t\t\t\tthis.addInput( p.name, LGShaders.ALL_TYPES );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderFunc.prototype.getTitle = function()\r\n\t{\r\n\t\tif(this.flags.collapsed)\r\n\t\t\treturn this.properties.func;\r\n\t\telse\r\n\t\t\treturn \"Func\";\r\n\t}\r\n\r\n\tLGraphShaderFunc.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination)\r\n\t\t\treturn;\r\n\r\n\t\tif(!this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlinks = [];\r\n\t\tfor(var i = 0; i < 3; ++i)\r\n\t\t\tinlinks.push( { name: getInputLinkID(this,i), type: this.getInputData(i) || \"float\" } );\r\n\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\t\tif(!outlink) //not connected\r\n\t\t\treturn;\r\n\r\n\t\tvar func_desc = GLSL_functions[ this.properties.func ];\r\n\t\tif(!func_desc)\r\n\t\t\treturn;\r\n\r\n\t\t//func_desc\r\n\t\tvar base_type = inlinks[0].type;\r\n\t\tvar return_type = func_desc.return_type;\r\n\t\tif( return_type == \"T\" )\r\n\t\t\treturn_type = base_type;\r\n\r\n\t\tvar params = [];\r\n\t\tfor(var i = 0; i < func_desc.params.length; ++i)\r\n\t\t{\r\n\t\t\tvar p = func_desc.params[i];\r\n\t\t\tvar param_code = inlinks[i].name;\r\n\t\t\tif(param_code == null) //not plugged\r\n\t\t\t{\r\n\t\t\t\tparam_code = p.value != null ? p.value : \"(1.0)\";\r\n\t\t\t\tinlinks[i].type = \"float\";\r\n\t\t\t}\r\n\t\t\tif( (p.type == \"T\" && inlinks[i].type != base_type) ||\r\n\t\t\t\t(p.type != \"T\" && inlinks[i].type != base_type) )\r\n\t\t\t\tparam_code = convertVarToGLSLType( param_code, inlinks[i].type, base_type );\r\n\t\t\tparams.push( param_code );\r\n\t\t}\r\n\t\t\r\n\t\tcontext.addFunction(\"round\",\"float round(float v){ return floor(v+0.5); }\\nvec2 round(vec2 v){ return floor(v+vec2(0.5));}\\nvec3 round(vec3 v){ return floor(v+vec3(0.5));}\\nvec4 round(vec4 v){ return floor(v+vec4(0.5)); }\\n\");\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = \"+func_desc.func+\"(\"+params.join(\",\")+\");\", this.shader_destination );\r\n\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"math/func\", LGraphShaderFunc );\r\n\r\n\r\n\r\n\tfunction LGraphShaderSnippet()\r\n\t{\r\n\t\tthis.addInput(\"A\", LGShaders.ALL_TYPES );\r\n\t\tthis.addInput(\"B\", LGShaders.ALL_TYPES );\r\n\t\tthis.addOutput(\"C\",\"vec4\");\r\n\t\tthis.properties = {\r\n\t\t\tcode:\"C = A+B\",\r\n\t\t\ttype: \"vec4\"\r\n\t\t}\r\n\t\tthis.addWidget(\"text\",\"code\",this.properties.code,{ property: \"code\" });\r\n\t\tthis.addWidget(\"combo\",\"type\",this.properties.type,{ values:[\"float\",\"vec2\",\"vec3\",\"vec4\"], property: \"type\" });\r\n\t}\r\n\r\n\tLGraphShaderSnippet.title = \"Snippet\";\r\n\r\n\tLGraphShaderSnippet.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\tthis.graph._version++;\r\n\r\n\t\tif(name == \"type\"&& this.outputs[0].type != value)\r\n\t\t{\r\n\t\t\tthis.disconnectOutput(0);\r\n\t\t\tthis.outputs[0].type = value;\r\n\t\t}\r\n\t}\r\n\r\n\tLGraphShaderSnippet.prototype.getTitle = function()\r\n\t{\r\n\t\tif(this.flags.collapsed)\r\n\t\t\treturn this.properties.code;\r\n\t\telse\r\n\t\t\treturn \"Snippet\";\r\n\t}\r\n\r\n\tLGraphShaderSnippet.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlinkA = getInputLinkID(this,0);\r\n\t\tif(!inlinkA)\r\n\t\t\tinlinkA = \"1.0\";\r\n\t\tvar inlinkB = getInputLinkID(this,1);\r\n\t\tif(!inlinkB)\r\n\t\t\tinlinkB = \"1.0\";\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\t\tif(!outlink) //not connected\r\n\t\t\treturn;\r\n\r\n\t\tvar inA_type = this.getInputData(0) || \"float\";\r\n\t\tvar inB_type = this.getInputData(1) || \"float\";\r\n\t\tvar return_type = this.properties.type;\r\n\r\n\t\t//cannot resolve input\r\n\t\tif(inA_type == \"T\" || inB_type == \"T\")\r\n\t\t{\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\tvar funcname = \"funcSnippet\" + this.id;\r\n\r\n\t\tvar func_code = \"\\n\" + return_type + \" \" + funcname + \"( \" + inA_type + \" A, \" + inB_type + \" B) {\\n\";\r\n\t\tfunc_code += \"\t\" + return_type + \" C = \" + return_type + \"(0.0);\\n\";\r\n\t\tfunc_code += \"\t\" + this.properties.code + \";\\n\";\r\n\t\tfunc_code += \"\treturn C;\\n}\\n\";\r\n\r\n\t\tcontext.addCode(\"functions\", func_code, this.shader_destination );\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = \"+funcname+\"(\"+inlinkA+\",\"+inlinkB+\");\", this.shader_destination );\r\n\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"utils/snippet\", LGraphShaderSnippet );\r\n\r\n\t//************************************\r\n\r\n\tfunction LGraphShaderRand()\r\n\t{\r\n\t\tthis.addOutput(\"out\",\"float\");\r\n\t}\r\n\r\n\tLGraphShaderRand.title = \"Rand\";\r\n\r\n\tLGraphShaderRand.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\r\n\t\tcontext.addUniform( \"u_rand\" + this.id, \"float\", function(){ return Math.random(); });\r\n\t\tcontext.addCode(\"code\", \"float \" + outlink + \" = u_rand\" + this.id +\";\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, \"float\" );\r\n\t}\r\n\r\n\tregisterShaderNode( \"input/rand\", LGraphShaderRand );\r\n\r\n\t//noise\r\n\t//https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83\r\n\tfunction LGraphShaderNoise()\r\n\t{\r\n\t\tthis.addInput(\"out\", LGShaders.ALL_TYPES );\r\n\t\tthis.addInput(\"scale\", \"float\" );\r\n\t\tthis.addOutput(\"out\",\"float\");\r\n\t\tthis.properties = {\r\n\t\t\ttype: \"noise\",\r\n\t\t\tscale: 1\r\n\t\t};\r\n\t\tthis.addWidget(\"combo\",\"type\", this.properties.type, { property: \"type\", values: LGraphShaderNoise.NOISE_TYPES });\r\n\t\tthis.addWidget(\"number\",\"scale\", this.properties.scale, { property: \"scale\" });\r\n\t}\r\n\r\n\tLGraphShaderNoise.NOISE_TYPES = [\"noise\",\"rand\"];\r\n\r\n\tLGraphShaderNoise.title = \"noise\";\r\n\r\n\tLGraphShaderNoise.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlink = getInputLinkID(this,0);\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\r\n\t\tvar intype = this.getInputData(0);\r\n\t\tif(!inlink)\r\n\t\t{\r\n\t\t\tintype = \"vec2\";\r\n\t\t\tinlink = context.buffer_names.uvs;\r\n\t\t}\r\n\r\n\t\tcontext.addFunction(\"noise\",LGraphShaderNoise.shader_functions);\r\n\t\tcontext.addUniform( \"u_noise_scale\" + this.id, \"float\", this.properties.scale );\r\n\t\tif( intype == \"float\" )\r\n\t\t\tcontext.addCode(\"code\", \"float \" + outlink + \" = snoise( vec2(\" + inlink +\") * u_noise_scale\" + this.id +\");\", this.shader_destination );\r\n\t\telse if( intype == \"vec2\" || intype == \"vec3\" )\r\n\t\t\tcontext.addCode(\"code\", \"float \" + outlink + \" = snoise(\" + inlink +\" * u_noise_scale\" + this.id +\");\", this.shader_destination );\r\n\t\telse if( intype == \"vec4\" )\r\n\t\t\tcontext.addCode(\"code\", \"float \" + outlink + \" = snoise(\" + inlink +\".xyz * u_noise_scale\" + this.id +\");\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, \"float\" );\r\n\t}\r\n\r\n\tregisterShaderNode( \"math/noise\", LGraphShaderNoise );\r\n\r\nLGraphShaderNoise.shader_functions = \"\\n\\\r\nvec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }\\n\\\r\n\\n\\\r\nfloat snoise(vec2 v){\\n\\\r\n  const vec4 C = vec4(0.211324865405187, 0.366025403784439,-0.577350269189626, 0.024390243902439);\\n\\\r\n  vec2 i  = floor(v + dot(v, C.yy) );\\n\\\r\n  vec2 x0 = v -   i + dot(i, C.xx);\\n\\\r\n  vec2 i1;\\n\\\r\n  i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\\n\\\r\n  vec4 x12 = x0.xyxy + C.xxzz;\\n\\\r\n  x12.xy -= i1;\\n\\\r\n  i = mod(i, 289.0);\\n\\\r\n  vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))\\n\\\r\n  + i.x + vec3(0.0, i1.x, 1.0 ));\\n\\\r\n  vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),dot(x12.zw,x12.zw)), 0.0);\\n\\\r\n  m = m*m ;\\n\\\r\n  m = m*m ;\\n\\\r\n  vec3 x = 2.0 * fract(p * C.www) - 1.0;\\n\\\r\n  vec3 h = abs(x) - 0.5;\\n\\\r\n  vec3 ox = floor(x + 0.5);\\n\\\r\n  vec3 a0 = x - ox;\\n\\\r\n  m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );\\n\\\r\n  vec3 g;\\n\\\r\n  g.x  = a0.x  * x0.x  + h.x  * x0.y;\\n\\\r\n  g.yz = a0.yz * x12.xz + h.yz * x12.yw;\\n\\\r\n  return 130.0 * dot(m, g);\\n\\\r\n}\\n\\\r\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\\n\\\r\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\\n\\\r\n\\n\\\r\nfloat snoise(vec3 v){ \\n\\\r\n  const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;\\n\\\r\n  const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);\\n\\\r\n\\n\\\r\n// First corner\\n\\\r\n  vec3 i  = floor(v + dot(v, C.yyy) );\\n\\\r\n  vec3 x0 =   v - i + dot(i, C.xxx) ;\\n\\\r\n\\n\\\r\n// Other corners\\n\\\r\n  vec3 g = step(x0.yzx, x0.xyz);\\n\\\r\n  vec3 l = 1.0 - g;\\n\\\r\n  vec3 i1 = min( g.xyz, l.zxy );\\n\\\r\n  vec3 i2 = max( g.xyz, l.zxy );\\n\\\r\n\\n\\\r\n  //  x0 = x0 - 0. + 0.0 * C \\n\\\r\n  vec3 x1 = x0 - i1 + 1.0 * C.xxx;\\n\\\r\n  vec3 x2 = x0 - i2 + 2.0 * C.xxx;\\n\\\r\n  vec3 x3 = x0 - 1. + 3.0 * C.xxx;\\n\\\r\n\\n\\\r\n// Permutations\\n\\\r\n  i = mod(i, 289.0 ); \\n\\\r\n  vec4 p = permute( permute( permute( \\n\\\r\n             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))\\n\\\r\n           + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) \\n\\\r\n           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));\\n\\\r\n\\n\\\r\n// Gradients\\n\\\r\n// ( N*N points uniformly over a square, mapped onto an octahedron.)\\n\\\r\n  float n_ = 1.0/7.0; // N=7\\n\\\r\n  vec3  ns = n_ * D.wyz - D.xzx;\\n\\\r\n\\n\\\r\n  vec4 j = p - 49.0 * floor(p * ns.z *ns.z);  //  mod(p,N*N)\\n\\\r\n\\n\\\r\n  vec4 x_ = floor(j * ns.z);\\n\\\r\n  vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)\\n\\\r\n\\n\\\r\n  vec4 x = x_ *ns.x + ns.yyyy;\\n\\\r\n  vec4 y = y_ *ns.x + ns.yyyy;\\n\\\r\n  vec4 h = 1.0 - abs(x) - abs(y);\\n\\\r\n\\n\\\r\n  vec4 b0 = vec4( x.xy, y.xy );\\n\\\r\n  vec4 b1 = vec4( x.zw, y.zw );\\n\\\r\n\\n\\\r\n  vec4 s0 = floor(b0)*2.0 + 1.0;\\n\\\r\n  vec4 s1 = floor(b1)*2.0 + 1.0;\\n\\\r\n  vec4 sh = -step(h, vec4(0.0));\\n\\\r\n\\n\\\r\n  vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;\\n\\\r\n  vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;\\n\\\r\n\\n\\\r\n  vec3 p0 = vec3(a0.xy,h.x);\\n\\\r\n  vec3 p1 = vec3(a0.zw,h.y);\\n\\\r\n  vec3 p2 = vec3(a1.xy,h.z);\\n\\\r\n  vec3 p3 = vec3(a1.zw,h.w);\\n\\\r\n\\n\\\r\n//Normalise gradients\\n\\\r\n  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));\\n\\\r\n  p0 *= norm.x;\\n\\\r\n  p1 *= norm.y;\\n\\\r\n  p2 *= norm.z;\\n\\\r\n  p3 *= norm.w;\\n\\\r\n\\n\\\r\n// Mix final noise value\\n\\\r\n  vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);\\n\\\r\n  m = m * m;\\n\\\r\n  return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),dot(p2,x2), dot(p3,x3) ) );\\n\\\r\n}\\n\\\r\n\\n\\\r\nvec3 hash3( vec2 p ){\\n\\\r\n    vec3 q = vec3( dot(p,vec2(127.1,311.7)), \\n\\\r\n\t\t\t\t   dot(p,vec2(269.5,183.3)), \\n\\\r\n\t\t\t\t   dot(p,vec2(419.2,371.9)) );\\n\\\r\n\treturn fract(sin(q)*43758.5453);\\n\\\r\n}\\n\\\r\nvec4 hash4( vec3 p ){\\n\\\r\n    vec4 q = vec4( dot(p,vec3(127.1,311.7,257.3)), \\n\\\r\n\t\t\t\t   dot(p,vec3(269.5,183.3,335.1)), \\n\\\r\n\t\t\t\t   dot(p,vec3(314.5,235.1,467.3)), \\n\\\r\n\t\t\t\t   dot(p,vec3(419.2,371.9,114.9)) );\\n\\\r\n\treturn fract(sin(q)*43758.5453);\\n\\\r\n}\\n\\\r\n\\n\\\r\nfloat iqnoise( in vec2 x, float u, float v ){\\n\\\r\n    vec2 p = floor(x);\\n\\\r\n    vec2 f = fract(x);\\n\\\r\n\t\\n\\\r\n\tfloat k = 1.0+63.0*pow(1.0-v,4.0);\\n\\\r\n\t\\n\\\r\n\tfloat va = 0.0;\\n\\\r\n\tfloat wt = 0.0;\\n\\\r\n    for( int j=-2; j<=2; j++ )\\n\\\r\n    for( int i=-2; i<=2; i++ )\\n\\\r\n    {\\n\\\r\n        vec2 g = vec2( float(i),float(j) );\\n\\\r\n\t\tvec3 o = hash3( p + g )*vec3(u,u,1.0);\\n\\\r\n\t\tvec2 r = g - f + o.xy;\\n\\\r\n\t\tfloat d = dot(r,r);\\n\\\r\n\t\tfloat ww = pow( 1.0-smoothstep(0.0,1.414,sqrt(d)), k );\\n\\\r\n\t\tva += o.z*ww;\\n\\\r\n\t\twt += ww;\\n\\\r\n    }\\n\\\r\n\t\\n\\\r\n    return va/wt;\\n\\\r\n}\\n\\\r\n\"\r\n\r\n\tfunction LGraphShaderTime()\r\n\t{\r\n\t\tthis.addOutput(\"out\",\"float\");\r\n\t}\r\n\r\n\tLGraphShaderTime.title = \"Time\";\r\n\r\n\tLGraphShaderTime.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\r\n\t\tcontext.addUniform( \"u_time\" + this.id, \"float\", function(){ return getTime() * 0.001; });\r\n\t\tcontext.addCode(\"code\", \"float \" + outlink + \" = u_time\" + this.id +\";\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, \"float\" );\r\n\t}\r\n\r\n\tregisterShaderNode( \"input/time\", LGraphShaderTime );\r\n\r\n\r\n\tfunction LGraphShaderDither()\r\n\t{\r\n\t\tthis.addInput(\"in\",\"T\");\r\n\t\tthis.addOutput(\"out\",\"float\");\r\n\t}\r\n\r\n\tLGraphShaderDither.title = \"Dither\";\r\n\r\n\tLGraphShaderDither.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlink = getInputLinkID(this,0);\r\n\t\tvar return_type = \"float\";\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\t\tvar intype = this.getInputData(0);\r\n\t\tinlink = varToTypeGLSL( inlink, intype, \"float\" );\r\n\t\tcontext.addFunction(\"dither8x8\", LGraphShaderDither.dither_func);\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = dither8x8(\"+ inlink +\");\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tLGraphShaderDither.dither_values = [0.515625,0.140625,0.640625,0.046875,0.546875,0.171875,0.671875,0.765625,0.265625,0.890625,0.390625,0.796875,0.296875,0.921875,0.421875,0.203125,0.703125,0.078125,0.578125,0.234375,0.734375,0.109375,0.609375,0.953125,0.453125,0.828125,0.328125,0.984375,0.484375,0.859375,0.359375,0.0625,0.5625,0.1875,0.6875,0.03125,0.53125,0.15625,0.65625,0.8125,0.3125,0.9375,0.4375,0.78125,0.28125,0.90625,0.40625,0.25,0.75,0.125,0.625,0.21875,0.71875,0.09375,0.59375,1.0001,0.5,0.875,0.375,0.96875,0.46875,0.84375,0.34375];\r\n\t\r\n\tLGraphShaderDither.dither_func = \"\\n\\\r\n\t\tfloat dither8x8(float brightness) {\\n\\\r\n\t\t  vec2 position = vec2(0.0);\\n\\\r\n\t\t  #ifdef FRAGMENT\\n\\\r\n\t\t\tposition = gl_FragCoord.xy;\\n\\\r\n\t\t  #endif\\n\\\r\n\t\t  int x = int(mod(position.x, 8.0));\\n\\\r\n\t\t  int y = int(mod(position.y, 8.0));\\n\\\r\n\t\t  int index = x + y * 8;\\n\\\r\n\t\t  float limit = 0.0;\\n\\\r\n\t\t  if (x < 8) {\\n\\\r\n\t\t\tif(index==0) limit = 0.015625;\\n\\\r\n\t\t\t\"+(LGraphShaderDither.dither_values.map( function(v,i){ return \"else if(index== \"+(i+1)+\") limit = \" + v + \";\"}).join(\"\\n\"))+\"\\n\\\r\n\t\t  }\\n\\\r\n\t\t  return brightness < limit ? 0.0 : 1.0;\\n\\\r\n\t\t}\\n\",\r\n\r\n\tregisterShaderNode( \"math/dither\", LGraphShaderDither );\r\n\r\n\tfunction LGraphShaderRemap()\r\n\t{\r\n\t\tthis.addInput(\"\", LGShaders.ALL_TYPES );\r\n\t\tthis.addOutput(\"\",\"\");\r\n\t\tthis.properties = {\r\n\t\t\tmin_value: 0,\r\n\t\t\tmax_value: 1,\r\n\t\t\tmin_value2: 0,\r\n\t\t\tmax_value2: 1\r\n\t\t};\r\n\t\tthis.addWidget(\"number\",\"min\",0,{ step: 0.1, property: \"min_value\" });\r\n\t\tthis.addWidget(\"number\",\"max\",1,{ step: 0.1, property: \"max_value\" });\r\n\t\tthis.addWidget(\"number\",\"min2\",0,{ step: 0.1, property: \"min_value2\"});\r\n\t\tthis.addWidget(\"number\",\"max2\",1,{ step: 0.1, property: \"max_value2\"});\r\n\t}\r\n\r\n\tLGraphShaderRemap.title = \"Remap\";\r\n\r\n\tLGraphShaderRemap.prototype.onPropertyChanged = function()\r\n\t{\r\n\t\tif(this.graph)\r\n\t\t\tthis.graph._version++;\r\n\t}\r\n\r\n\tLGraphShaderRemap.prototype.onConnectionsChange = function()\r\n\t{\r\n\t\tvar return_type = this.getInputDataType(0);\r\n\t\tthis.outputs[0].type = return_type || \"T\";\r\n\t}\r\n\r\n\tLGraphShaderRemap.prototype.onGetCode = function( context )\r\n\t{\r\n\t\tif(!this.shader_destination || !this.isOutputConnected(0))\r\n\t\t\treturn;\r\n\r\n\t\tvar inlink = getInputLinkID(this,0);\r\n\t\tvar outlink = getOutputLinkID(this,0);\r\n\t\tif(!inlink && !outlink) //not connected\r\n\t\t\treturn;\r\n\r\n\t\tvar return_type = this.getInputDataType(0);\r\n\t\tthis.outputs[0].type = return_type;\r\n\t\tif(return_type == \"T\")\r\n\t\t{\r\n\t\t\tconsole.warn(\"node type is T and cannot be resolved\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif(!inlink)\r\n\t\t{\r\n\t\t\tcontext.addCode(\"code\",\"\t\" + return_type + \" \" + outlink + \" = \" + return_type + \"(0.0);\\n\");\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar minv = valueToGLSL( this.properties.min_value );\r\n\t\tvar maxv = valueToGLSL( this.properties.max_value );\r\n\t\tvar minv2 = valueToGLSL( this.properties.min_value2 );\r\n\t\tvar maxv2 = valueToGLSL( this.properties.max_value2 );\r\n\r\n\t\tcontext.addCode(\"code\", return_type + \" \" + outlink + \" = ( (\" + inlink + \" - \"+minv+\") / (\"+ maxv+\" - \"+minv+\") ) * (\"+ maxv2+\" - \"+minv2+\") + \" + minv2 + \";\", this.shader_destination );\r\n\t\tthis.setOutputData( 0, return_type );\r\n\t}\r\n\r\n\tregisterShaderNode( \"math/remap\", LGraphShaderRemap );\r\n\r\n})(this);\r\n\r\n\r\n"
  },
  {
    "path": "src/nodes/gltextures.js",
    "content": "(function(global) {\n    var LiteGraph = global.LiteGraph;\n\tvar LGraphCanvas = global.LGraphCanvas;\n\n    //Works with Litegl.js to create WebGL nodes\n    global.LGraphTexture = null;\n\n    if (typeof GL == \"undefined\")\n\t\treturn;\n\n\tLGraphCanvas.link_type_colors[\"Texture\"] = \"#987\";\n\n\tfunction LGraphTexture() {\n\t\tthis.addOutput(\"tex\", \"Texture\");\n\t\tthis.addOutput(\"name\", \"string\");\n\t\tthis.properties = { name: \"\", filter: true };\n\t\tthis.size = [\n\t\t\tLGraphTexture.image_preview_size,\n\t\t\tLGraphTexture.image_preview_size\n\t\t];\n\t}\n\n\tglobal.LGraphTexture = LGraphTexture;\n\n\tLGraphTexture.title = \"Texture\";\n\tLGraphTexture.desc = \"Texture\";\n\tLGraphTexture.widgets_info = {\n\t\tname: { widget: \"texture\" },\n\t\tfilter: { widget: \"checkbox\" }\n\t};\n\n\t//REPLACE THIS TO INTEGRATE WITH YOUR FRAMEWORK\n\tLGraphTexture.loadTextureCallback = null; //function in charge of loading textures when not present in the container\n\tLGraphTexture.image_preview_size = 256;\n\n\t//flags to choose output texture type\n\tLGraphTexture.UNDEFINED = 0; //not specified\n\tLGraphTexture.PASS_THROUGH = 1; //do not apply FX (like disable but passing the in to the out)\n\tLGraphTexture.COPY = 2; //create new texture with the same properties as the origin texture\n\tLGraphTexture.LOW = 3; //create new texture with low precision (byte)\n\tLGraphTexture.HIGH = 4; //create new texture with high precision (half-float)\n\tLGraphTexture.REUSE = 5; //reuse input texture\n\tLGraphTexture.DEFAULT = 2; //use the default\n\n\tLGraphTexture.MODE_VALUES = {\n\t\t\"undefined\": LGraphTexture.UNDEFINED,\n\t\t\"pass through\": LGraphTexture.PASS_THROUGH,\n\t\tcopy: LGraphTexture.COPY,\n\t\tlow: LGraphTexture.LOW,\n\t\thigh: LGraphTexture.HIGH,\n\t\treuse: LGraphTexture.REUSE,\n\t\tdefault: LGraphTexture.DEFAULT\n\t};\n\n\t//returns the container where all the loaded textures are stored (overwrite if you have a Resources Manager)\n\tLGraphTexture.getTexturesContainer = function() {\n\t\treturn gl.textures;\n\t};\n\n\t//process the loading of a texture (overwrite it if you have a Resources Manager)\n\tLGraphTexture.loadTexture = function(name, options) {\n\t\toptions = options || {};\n\t\tvar url = name;\n\t\tif (url.substr(0, 7) == \"http://\") {\n\t\t\tif (LiteGraph.proxy) {\n\t\t\t\t//proxy external files\n\t\t\t\turl = LiteGraph.proxy + url.substr(7);\n\t\t\t}\n\t\t}\n\n\t\tvar container = LGraphTexture.getTexturesContainer();\n\t\tvar tex = (container[name] = GL.Texture.fromURL(url, options));\n\t\treturn tex;\n\t};\n\n\tLGraphTexture.getTexture = function(name) {\n\t\tvar container = this.getTexturesContainer();\n\n\t\tif (!container) {\n\t\t\tthrow \"Cannot load texture, container of textures not found\";\n\t\t}\n\n\t\tvar tex = container[name];\n\t\tif (!tex && name && name[0] != \":\") {\n\t\t\treturn this.loadTexture(name);\n\t\t}\n\n\t\treturn tex;\n\t};\n\n\t//used to compute the appropiate output texture\n\tLGraphTexture.getTargetTexture = function(origin, target, mode) {\n\t\tif (!origin) {\n\t\t\tthrow \"LGraphTexture.getTargetTexture expects a reference texture\";\n\t\t}\n\n\t\tvar tex_type = null;\n\n\t\tswitch (mode) {\n\t\t\tcase LGraphTexture.LOW:\n\t\t\t\ttex_type = gl.UNSIGNED_BYTE;\n\t\t\t\tbreak;\n\t\t\tcase LGraphTexture.HIGH:\n\t\t\t\ttex_type = gl.HIGH_PRECISION_FORMAT;\n\t\t\t\tbreak;\n\t\t\tcase LGraphTexture.REUSE:\n\t\t\t\treturn origin;\n\t\t\t\tbreak;\n\t\t\tcase LGraphTexture.COPY:\n\t\t\tdefault:\n\t\t\t\ttex_type = origin ? origin.type : gl.UNSIGNED_BYTE;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (\n\t\t\t!target ||\n\t\t\ttarget.width != origin.width ||\n\t\t\ttarget.height != origin.height ||\n\t\t\ttarget.type != tex_type ||\n\t\t\ttarget.format != origin.format \n\t\t) {\n\t\t\ttarget = new GL.Texture(origin.width, origin.height, {\n\t\t\t\ttype: tex_type,\n\t\t\t\tformat: origin.format,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\treturn target;\n\t};\n\n\tLGraphTexture.getTextureType = function(precision, ref_texture) {\n\t\tvar type = ref_texture ? ref_texture.type : gl.UNSIGNED_BYTE;\n\t\tswitch (precision) {\n\t\t\tcase LGraphTexture.HIGH:\n\t\t\t\ttype = gl.HIGH_PRECISION_FORMAT;\n\t\t\t\tbreak;\n\t\t\tcase LGraphTexture.LOW:\n\t\t\t\ttype = gl.UNSIGNED_BYTE;\n\t\t\t\tbreak;\n\t\t\t//no default\n\t\t}\n\t\treturn type;\n\t};\n\n\tLGraphTexture.getWhiteTexture = function() {\n\t\tif (this._white_texture) {\n\t\t\treturn this._white_texture;\n\t\t}\n\t\tvar texture = (this._white_texture = GL.Texture.fromMemory(\n\t\t\t1,\n\t\t\t1,\n\t\t\t[255, 255, 255, 255],\n\t\t\t{ format: gl.RGBA, wrap: gl.REPEAT, filter: gl.NEAREST }\n\t\t));\n\t\treturn texture;\n\t};\n\n\tLGraphTexture.getNoiseTexture = function() {\n\t\tif (this._noise_texture) {\n\t\t\treturn this._noise_texture;\n\t\t}\n\n\t\tvar noise = new Uint8Array(512 * 512 * 4);\n\t\tfor (var i = 0; i < 512 * 512 * 4; ++i) {\n\t\t\tnoise[i] = Math.random() * 255;\n\t\t}\n\n\t\tvar texture = GL.Texture.fromMemory(512, 512, noise, {\n\t\t\tformat: gl.RGBA,\n\t\t\twrap: gl.REPEAT,\n\t\t\tfilter: gl.NEAREST\n\t\t});\n\t\tthis._noise_texture = texture;\n\t\treturn texture;\n\t};\n\n\tLGraphTexture.prototype.onDropFile = function(data, filename, file) {\n\t\tif (!data) {\n\t\t\tthis._drop_texture = null;\n\t\t\tthis.properties.name = \"\";\n\t\t} else {\n\t\t\tvar texture = null;\n\t\t\tif (typeof data == \"string\") {\n\t\t\t\ttexture = GL.Texture.fromURL(data);\n\t\t\t} else if (filename.toLowerCase().indexOf(\".dds\") != -1) {\n\t\t\t\ttexture = GL.Texture.fromDDSInMemory(data);\n\t\t\t} else {\n\t\t\t\tvar blob = new Blob([file]);\n\t\t\t\tvar url = URL.createObjectURL(blob);\n\t\t\t\ttexture = GL.Texture.fromURL(url);\n\t\t\t}\n\n\t\t\tthis._drop_texture = texture;\n\t\t\tthis.properties.name = filename;\n\t\t}\n\t};\n\n\tLGraphTexture.prototype.getExtraMenuOptions = function(graphcanvas) {\n\t\tvar that = this;\n\t\tif (!this._drop_texture) {\n\t\t\treturn;\n\t\t}\n\t\treturn [\n\t\t\t{\n\t\t\t\tcontent: \"Clear\",\n\t\t\t\tcallback: function() {\n\t\t\t\t\tthat._drop_texture = null;\n\t\t\t\t\tthat.properties.name = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t];\n\t};\n\n\tLGraphTexture.prototype.onExecute = function() {\n\t\tvar tex = null;\n\t\tif (this.isOutputConnected(1)) {\n\t\t\ttex = this.getInputData(0);\n\t\t}\n\n\t\tif (!tex && this._drop_texture) {\n\t\t\ttex = this._drop_texture;\n\t\t}\n\n\t\tif (!tex && this.properties.name) {\n\t\t\ttex = LGraphTexture.getTexture(this.properties.name);\n\t\t}\n\n\t\tif (!tex) {\n\t\t\tthis.setOutputData( 0, null );\n\t\t\tthis.setOutputData( 1, \"\" );\n\t\t\treturn;\n\t\t}\n\n\t\tthis._last_tex = tex;\n\n\t\tif (this.properties.filter === false) {\n\t\t\ttex.setParameter(gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n\t\t} else {\n\t\t\ttex.setParameter(gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n\t\t}\n\n\t\tthis.setOutputData( 0, tex );\n\t\tthis.setOutputData( 1, tex.fullpath || tex.filename );\n\n\t\tfor (var i = 2; i < this.outputs.length; i++) {\n\t\t\tvar output = this.outputs[i];\n\t\t\tif (!output) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvar v = null;\n\t\t\tif (output.name == \"width\") {\n\t\t\t\tv = tex.width;\n\t\t\t} else if (output.name == \"height\") {\n\t\t\t\tv = tex.height;\n\t\t\t} else if (output.name == \"aspect\") {\n\t\t\t\tv = tex.width / tex.height;\n\t\t\t}\n\t\t\tthis.setOutputData(i, v);\n\t\t}\n\t};\n\n\tLGraphTexture.prototype.onResourceRenamed = function(\n\t\told_name,\n\t\tnew_name\n\t) {\n\t\tif (this.properties.name == old_name) {\n\t\t\tthis.properties.name = new_name;\n\t\t}\n\t};\n\n\tLGraphTexture.prototype.onDrawBackground = function(ctx) {\n\t\tif (this.flags.collapsed || this.size[1] <= 20) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this._drop_texture && ctx.webgl) {\n\t\t\tctx.drawImage(\n\t\t\t\tthis._drop_texture,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\tthis.size[0],\n\t\t\t\tthis.size[1]\n\t\t\t);\n\t\t\t//this._drop_texture.renderQuad(this.pos[0],this.pos[1],this.size[0],this.size[1]);\n\t\t\treturn;\n\t\t}\n\n\t\t//Different texture? then get it from the GPU\n\t\tif (this._last_preview_tex != this._last_tex) {\n\t\t\tif (ctx.webgl) {\n\t\t\t\tthis._canvas = this._last_tex;\n\t\t\t} else {\n\t\t\t\tvar tex_canvas = LGraphTexture.generateLowResTexturePreview(\n\t\t\t\t\tthis._last_tex\n\t\t\t\t);\n\t\t\t\tif (!tex_canvas) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis._last_preview_tex = this._last_tex;\n\t\t\t\tthis._canvas = cloneCanvas(tex_canvas);\n\t\t\t}\n\t\t}\n\n\t\tif (!this._canvas) {\n\t\t\treturn;\n\t\t}\n\n\t\t//render to graph canvas\n\t\tctx.save();\n\t\tif (!ctx.webgl) {\n\t\t\t//reverse image\n\t\t\tctx.translate(0, this.size[1]);\n\t\t\tctx.scale(1, -1);\n\t\t}\n\t\tctx.drawImage(this._canvas, 0, 0, this.size[0], this.size[1]);\n\t\tctx.restore();\n\t};\n\n\t//very slow, used at your own risk\n\tLGraphTexture.generateLowResTexturePreview = function(tex) {\n\t\tif (!tex) {\n\t\t\treturn null;\n\t\t}\n\n\t\tvar size = LGraphTexture.image_preview_size;\n\t\tvar temp_tex = tex;\n\n\t\tif (tex.format == gl.DEPTH_COMPONENT) {\n\t\t\treturn null;\n\t\t} //cannot generate from depth\n\n\t\t//Generate low-level version in the GPU to speed up\n\t\tif (tex.width > size || tex.height > size) {\n\t\t\ttemp_tex = this._preview_temp_tex;\n\t\t\tif (!this._preview_temp_tex) {\n\t\t\t\ttemp_tex = new GL.Texture(size, size, {\n\t\t\t\t\tminFilter: gl.NEAREST\n\t\t\t\t});\n\t\t\t\tthis._preview_temp_tex = temp_tex;\n\t\t\t}\n\n\t\t\t//copy\n\t\t\ttex.copyTo(temp_tex);\n\t\t\ttex = temp_tex;\n\t\t}\n\n\t\t//create intermediate canvas with lowquality version\n\t\tvar tex_canvas = this._preview_canvas;\n\t\tif (!tex_canvas) {\n\t\t\ttex_canvas = createCanvas(size, size);\n\t\t\tthis._preview_canvas = tex_canvas;\n\t\t}\n\n\t\tif (temp_tex) {\n\t\t\ttemp_tex.toCanvas(tex_canvas);\n\t\t}\n\t\treturn tex_canvas;\n\t};\n\n\tLGraphTexture.prototype.getResources = function(res) {\n\t\tif(this.properties.name)\n\t\t\tres[this.properties.name] = GL.Texture;\n\t\treturn res;\n\t};\n\n\tLGraphTexture.prototype.onGetInputs = function() {\n\t\treturn [[\"in\", \"Texture\"]];\n\t};\n\n\tLGraphTexture.prototype.onGetOutputs = function() {\n\t\treturn [\n\t\t\t[\"width\", \"number\"],\n\t\t\t[\"height\", \"number\"],\n\t\t\t[\"aspect\", \"number\"]\n\t\t];\n\t};\n\n\t//used to replace shader code\n\tLGraphTexture.replaceCode = function( code, context )\n\t{\n\t\treturn code.replace(/\\{\\{[a-zA-Z0-9_]*\\}\\}/g, function(v){\n\t\t\tv = v.replace( /[\\{\\}]/g, \"\" );\n\t\t\treturn context[v] || \"\";\n\t\t});\n\t}\n\n\tLiteGraph.registerNodeType(\"texture/texture\", LGraphTexture);\n\n\t//**************************\n\tfunction LGraphTexturePreview() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.properties = { flipY: false };\n\t\tthis.size = [\n\t\t\tLGraphTexture.image_preview_size,\n\t\t\tLGraphTexture.image_preview_size\n\t\t];\n\t}\n\n\tLGraphTexturePreview.title = \"Preview\";\n\tLGraphTexturePreview.desc = \"Show a texture in the graph canvas\";\n\tLGraphTexturePreview.allow_preview = false;\n\n\tLGraphTexturePreview.prototype.onDrawBackground = function(ctx) {\n\t\tif (this.flags.collapsed) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!ctx.webgl && !LGraphTexturePreview.allow_preview) {\n\t\t\treturn;\n\t\t} //not working well\n\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar tex_canvas = null;\n\n\t\tif (!tex.handle && ctx.webgl) {\n\t\t\ttex_canvas = tex;\n\t\t} else {\n\t\t\ttex_canvas = LGraphTexture.generateLowResTexturePreview(tex);\n\t\t}\n\n\t\t//render to graph canvas\n\t\tctx.save();\n\t\tif (this.properties.flipY) {\n\t\t\tctx.translate(0, this.size[1]);\n\t\t\tctx.scale(1, -1);\n\t\t}\n\t\tctx.drawImage(tex_canvas, 0, 0, this.size[0], this.size[1]);\n\t\tctx.restore();\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/preview\", LGraphTexturePreview);\n\n\t//**************************************\n\n\tfunction LGraphTextureSave() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"tex\", \"Texture\");\n\t\tthis.addOutput(\"name\", \"string\");\n\t\tthis.properties = { name: \"\", generate_mipmaps: false };\n\t}\n\n\tLGraphTextureSave.title = \"Save\";\n\tLGraphTextureSave.desc = \"Save a texture in the repository\";\n\n\tLGraphTextureSave.prototype.getPreviewTexture = function()\n\t{\n\t\treturn this._texture;\n\t}\n\n\tLGraphTextureSave.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.properties.generate_mipmaps) {\n\t\t\ttex.bind(0);\n\t\t\ttex.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR );\n\t\t\tgl.generateMipmap(tex.texture_type);\n\t\t\ttex.unbind(0);\n\t\t}\n\n\t\tif (this.properties.name) {\n\t\t\t//for cases where we want to perform something when storing it\n\t\t\tif (LGraphTexture.storeTexture) {\n\t\t\t\tLGraphTexture.storeTexture(this.properties.name, tex);\n\t\t\t} else {\n\t\t\t\tvar container = LGraphTexture.getTexturesContainer();\n\t\t\t\tcontainer[this.properties.name] = tex;\n\t\t\t}\n\t\t}\n\n\t\tthis._texture = tex;\n\t\tthis.setOutputData(0, tex);\n\t\tthis.setOutputData(1, this.properties.name);\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/save\", LGraphTextureSave);\n\n\t//****************************************************\n\n\tfunction LGraphTextureOperation() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addInput(\"TextureB\", \"Texture\");\n\t\tthis.addInput(\"value\", \"number\");\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\t\tthis.help = \"<p>pixelcode must be vec3, uvcode must be vec2, is optional</p>\\\n\t\t<p><strong>uv:</strong> tex. coords</p><p><strong>color:</strong> texture <strong>colorB:</strong> textureB</p><p><strong>time:</strong> scene time <strong>value:</strong> input value</p><p>For multiline you must type: result = ...</p>\";\n\n\t\tthis.properties = {\n\t\t\tvalue: 1,\n\t\t\tpixelcode: \"color + colorB * value\",\n\t\t\tuvcode: \"\",\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\n\t\tthis.has_error = false;\n\t}\n\n\tLGraphTextureOperation.widgets_info = {\n\t\tuvcode: { widget: \"code\" },\n\t\tpixelcode: { widget: \"code\" },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureOperation.title = \"Operation\";\n\tLGraphTextureOperation.desc = \"Texture shader operation\";\n\n\tLGraphTextureOperation.presets = {};\n\n\tLGraphTextureOperation.prototype.getExtraMenuOptions = function(\n\t\tgraphcanvas\n\t) {\n\t\tvar that = this;\n\t\tvar txt = !that.properties.show ? \"Show Texture\" : \"Hide Texture\";\n\t\treturn [\n\t\t\t{\n\t\t\t\tcontent: txt,\n\t\t\t\tcallback: function() {\n\t\t\t\t\tthat.properties.show = !that.properties.show;\n\t\t\t\t}\n\t\t\t}\n\t\t];\n\t};\n\n\tLGraphTextureOperation.prototype.onPropertyChanged = function()\n\t{\n\t\tthis.has_error = false;\n\t}\n\n\tLGraphTextureOperation.prototype.onDrawBackground = function(ctx) {\n\t\tif (\n\t\t\tthis.flags.collapsed ||\n\t\t\tthis.size[1] <= 20 ||\n\t\t\t!this.properties.show\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this._tex) {\n\t\t\treturn;\n\t\t}\n\n\t\t//only works if using a webgl renderer\n\t\tif (this._tex.gl != ctx) {\n\t\t\treturn;\n\t\t}\n\n\t\t//render to graph canvas\n\t\tctx.save();\n\t\tctx.drawImage(this._tex, 0, 0, this.size[0], this.size[1]);\n\t\tctx.restore();\n\t};\n\n\tLGraphTextureOperation.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar texB = this.getInputData(1);\n\n\t\tif (!this.properties.uvcode && !this.properties.pixelcode) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = 512;\n\t\tvar height = 512;\n\t\tif (tex) {\n\t\t\twidth = tex.width;\n\t\t\theight = tex.height;\n\t\t} else if (texB) {\n\t\t\twidth = texB.width;\n\t\t\theight = texB.height;\n\t\t}\n\n\t\tif(!texB)\n\t\t\ttexB = GL.Texture.getWhiteTexture();\n\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision, tex );\n\n\t\tif (!tex && !this._tex) {\n\t\t\tthis._tex = new GL.Texture(width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });\n\t\t} else {\n\t\t\tthis._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision );\n\t\t}\n\n\t\tvar uvcode = \"\";\n\t\tif (this.properties.uvcode) {\n\t\t\tuvcode = \"uv = \" + this.properties.uvcode;\n\t\t\tif (this.properties.uvcode.indexOf(\";\") != -1) {\n\t\t\t\t//there are line breaks, means multiline code\n\t\t\t\tuvcode = this.properties.uvcode;\n\t\t\t}\n\t\t}\n\n\t\tvar pixelcode = \"\";\n\t\tif (this.properties.pixelcode) {\n\t\t\tpixelcode = \"result = \" + this.properties.pixelcode;\n\t\t\tif (this.properties.pixelcode.indexOf(\";\") != -1) {\n\t\t\t\t//there are line breaks, means multiline code\n\t\t\t\tpixelcode = this.properties.pixelcode;\n\t\t\t}\n\t\t}\n\n\t\tvar shader = this._shader;\n\n\t\tif ( !this.has_error && (!shader || this._shader_code != uvcode + \"|\" + pixelcode) ) {\n\n\t\t\tvar final_pixel_code = LGraphTexture.replaceCode( LGraphTextureOperation.pixel_shader, { UV_CODE:uvcode, PIXEL_CODE:pixelcode });\n\n\t\t\ttry {\n\t\t\t\tshader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, final_pixel_code );\n\t\t\t\tthis.boxcolor = \"#00FF00\";\n\t\t\t} catch (err) {\n\t\t\t\t//console.log(\"Error compiling shader: \", err, final_pixel_code );\n\t\t\t\tGL.Shader.dumpErrorToConsole(err,Shader.SCREEN_VERTEX_SHADER, final_pixel_code);\n\t\t\t\tthis.boxcolor = \"#FF0000\";\n\t\t\t\tthis.has_error = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis._shader = shader;\n\t\t\tthis._shader_code = uvcode + \"|\" + pixelcode;\n\t\t}\n\n\t\tif(!this._shader)\n\t\t\treturn;\n\n\t\tvar value = this.getInputData(2);\n\t\tif (value != null) {\n\t\t\tthis.properties.value = value;\n\t\t} else {\n\t\t\tvalue = parseFloat(this.properties.value);\n\t\t}\n\n\t\tvar time = this.graph.getTime();\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\tgl.disable(gl.CULL_FACE);\n\t\t\tgl.disable(gl.BLEND);\n\t\t\tif (tex) {\n\t\t\t\ttex.bind(0);\n\t\t\t}\n\t\t\tif (texB) {\n\t\t\t\ttexB.bind(1);\n\t\t\t}\n\t\t\tvar mesh = Mesh.getScreenQuad();\n\t\t\tshader\n\t\t\t\t.uniforms({\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tu_textureB: 1,\n\t\t\t\t\tvalue: value,\n\t\t\t\t\ttexSize: [width, height,1/width,1/height],\n\t\t\t\t\ttime: time\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureOperation.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\t\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform vec4 texSize;\\n\\\n\t\tuniform float time;\\n\\\n\t\tuniform float value;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec2 uv = v_coord;\\n\\\n\t\t\t{{UV_CODE}};\\n\\\n\t\t\tvec4 color4 = texture2D(u_texture, uv);\\n\\\n\t\t\tvec3 color = color4.rgb;\\n\\\n\t\t\tvec4 color4B = texture2D(u_textureB, uv);\\n\\\n\t\t\tvec3 colorB = color4B.rgb;\\n\\\n\t\t\tvec3 result = color;\\n\\\n\t\t\tfloat alpha = 1.0;\\n\\\n\t\t\t{{PIXEL_CODE}};\\n\\\n\t\t\tgl_FragColor = vec4(result, alpha);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLGraphTextureOperation.registerPreset = function ( name, code )\n\t{\n\t\tLGraphTextureOperation.presets[name] = code;\n\t}\n\n\tLGraphTextureOperation.registerPreset(\"\",\"\");\n\tLGraphTextureOperation.registerPreset(\"bypass\",\"color\");\n\tLGraphTextureOperation.registerPreset(\"add\",\"color + colorB * value\");\n\tLGraphTextureOperation.registerPreset(\"substract\",\"(color - colorB) * value\");\n\tLGraphTextureOperation.registerPreset(\"mate\",\"mix( color, colorB, color4B.a * value)\");\n\tLGraphTextureOperation.registerPreset(\"invert\",\"vec3(1.0) - color\");\n\tLGraphTextureOperation.registerPreset(\"multiply\",\"color * colorB * value\");\n\tLGraphTextureOperation.registerPreset(\"divide\",\"(color / colorB) / value\");\n\tLGraphTextureOperation.registerPreset(\"difference\",\"abs(color - colorB) * value\");\n\tLGraphTextureOperation.registerPreset(\"max\",\"max(color, colorB) * value\");\n\tLGraphTextureOperation.registerPreset(\"min\",\"min(color, colorB) * value\");\n\tLGraphTextureOperation.registerPreset(\"displace\",\"texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz\");\n\tLGraphTextureOperation.registerPreset(\"grayscale\",\"vec3(color.x + color.y + color.z) * value / 3.0\");\n\tLGraphTextureOperation.registerPreset(\"saturation\",\"mix( vec3(color.x + color.y + color.z) / 3.0, color, value )\");\n\tLGraphTextureOperation.registerPreset(\"normalmap\",\"\\n\\\n\t\tfloat z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\\n\\\n\t\tfloat z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\\n\\\n\t\tfloat z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\\n\\\n\t\tfloat z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\\n\\\n\t\tfloat z4 = color.x;\\n\\\n\t\tfloat z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\\n\\\n\t\tfloat z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\\n\\\n\t\tfloat z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\\n\\\n\t\tfloat z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\\n\\\n\t\tvec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\\n\\\n\t\tnormal.xy *= value;\\n\\\n\t\tresult.xyz = normalize(normal) * 0.5 + vec3(0.5);\\n\\\n\t\");\n\tLGraphTextureOperation.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)\");\n\n\t//webglstudio stuff...\n\tLGraphTextureOperation.prototype.onInspect = function(widgets)\n\t{\n\t\tvar that = this;\n\t\twidgets.addCombo(\"Presets\",\"\",{ values: Object.keys(LGraphTextureOperation.presets), callback: function(v){\n\t\t\tvar code = LGraphTextureOperation.presets[v];\n\t\t\tif(!code)\n\t\t\t\treturn;\n\t\t\tthat.setProperty(\"pixelcode\",code);\n\t\t\tthat.title = v;\n\t\t\twidgets.refresh();\n\t\t}});\n\t}\n\n\tLiteGraph.registerNodeType(\"texture/operation\", LGraphTextureOperation);\n\n\t//****************************************************\n\n\tfunction LGraphTextureShader() {\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tcode: \"\",\n\t\t\tu_value: 1,\n\t\t\tu_color: [1,1,1,1],\n\t\t\twidth: 512,\n\t\t\theight: 512,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\n\t\tthis.properties.code = LGraphTextureShader.pixel_shader;\n\t\tthis._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec4.create(), time: 0 };\n\t}\n\n\tLGraphTextureShader.title = \"Shader\";\n\tLGraphTextureShader.desc = \"Texture shader\";\n\tLGraphTextureShader.widgets_info = {\n\t\tcode: { type: \"code\", lang: \"glsl\" },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureShader.prototype.onPropertyChanged = function(\n\t\tname,\n\t\tvalue\n\t) {\n\t\tif (name != \"code\") {\n\t\t\treturn;\n\t\t}\n\n\t\tvar shader = this.getShader();\n\t\tif (!shader) {\n\t\t\treturn;\n\t\t}\n\n\t\t//update connections\n\t\tvar uniforms = shader.uniformInfo;\n\n\t\t//remove deprecated slots\n\t\tif (this.inputs) {\n\t\t\tvar already = {};\n\t\t\tfor (var i = 0; i < this.inputs.length; ++i) {\n\t\t\t\tvar info = this.getInputInfo(i);\n\t\t\t\tif (!info) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (uniforms[info.name] && !already[info.name]) {\n\t\t\t\t\talready[info.name] = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthis.removeInput(i);\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\n\t\t//update existing ones\n\t\tfor (var i in uniforms) {\n\t\t\tvar info = shader.uniformInfo[i];\n\t\t\tif (info.loc === null) {\n\t\t\t\tcontinue;\n\t\t\t} //is an attribute, not a uniform\n\t\t\tif (i == \"time\") {\n\t\t\t\t//default one\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar type = \"number\";\n\t\t\tif (this._shader.samplers[i]) {\n\t\t\t\ttype = \"texture\";\n\t\t\t} else {\n\t\t\t\tswitch (info.size) {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\ttype = \"number\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\ttype = \"vec2\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\ttype = \"vec3\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\n\t\t\t\t\t\ttype = \"vec4\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 9:\n\t\t\t\t\t\ttype = \"mat3\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 16:\n\t\t\t\t\t\ttype = \"mat4\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar slot = this.findInputSlot(i);\n\t\t\tif (slot == -1) {\n\t\t\t\tthis.addInput(i, type);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar input_info = this.getInputInfo(slot);\n\t\t\tif (!input_info) {\n\t\t\t\tthis.addInput(i, type);\n\t\t\t} else {\n\t\t\t\tif (input_info.type == type) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthis.removeInput(slot, type);\n\t\t\t\tthis.addInput(i, type);\n\t\t\t}\n\t\t}\n\t};\n\n\tLGraphTextureShader.prototype.getShader = function() {\n\t\t//replug\n\t\tif (this._shader && this._shader_code == this.properties.code) {\n\t\t\treturn this._shader;\n\t\t}\n\n\t\tthis._shader_code = this.properties.code;\n\t\tthis._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, this.properties.code );\n\t\tif (!this._shader) {\n\t\t\tthis.boxcolor = \"red\";\n\t\t\treturn null;\n\t\t} else {\n\t\t\tthis.boxcolor = \"green\";\n\t\t}\n\t\treturn this._shader;\n\t};\n\n\tLGraphTextureShader.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar shader = this.getShader();\n\t\tif (!shader) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar tex_slot = 0;\n\t\tvar in_tex = null;\n\n\t\t//set uniforms\n\t\tif(this.inputs)\n\t\tfor (var i = 0; i < this.inputs.length; ++i) {\n\t\t\tvar info = this.getInputInfo(i);\n\t\t\tvar data = this.getInputData(i);\n\t\t\tif (data == null) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (data.constructor === GL.Texture) {\n\t\t\t\tdata.bind(tex_slot);\n\t\t\t\tif (!in_tex) {\n\t\t\t\t\tin_tex = data;\n\t\t\t\t}\n\t\t\t\tdata = tex_slot;\n\t\t\t\ttex_slot++;\n\t\t\t}\n\t\t\tshader.setUniform(info.name, data); //data is tex_slot\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision, in_tex );\n\n\t\t//render to texture\n\t\tvar w = this.properties.width | 0;\n\t\tvar h = this.properties.height | 0;\n\t\tif (w == 0) {\n\t\t\tw = in_tex ? in_tex.width : gl.canvas.width;\n\t\t}\n\t\tif (h == 0) {\n\t\t\th = in_tex ? in_tex.height : gl.canvas.height;\n\t\t}\n\t\tuniforms.texSize[0] = w;\n\t\tuniforms.texSize[1] = h;\n\t\tuniforms.texSize[2] = 1/w;\n\t\tuniforms.texSize[3] = 1/h;\n\t\tuniforms.time = this.graph.getTime();\n\t\tuniforms.u_value = this.properties.u_value;\n\t\tuniforms.u_color.set( this.properties.u_color );\n\n\t\tif ( !this._tex || this._tex.type != type ||  this._tex.width != w || this._tex.height != h ) {\n\t\t\tthis._tex = new GL.Texture(w, h, {  type: type, format: gl.RGBA, filter: gl.LINEAR });\n\t\t}\n\t\tvar tex = this._tex;\n\t\ttex.drawTo(function() {\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureShader.pixel_shader =\n\"precision highp float;\\n\\\n\\n\\\nvarying vec2 v_coord;\\n\\\nuniform float time; //time in seconds\\n\\\nuniform vec4 texSize; //tex resolution\\n\\\nuniform float u_value;\\n\\\nuniform vec4 u_color;\\n\\n\\\nvoid main() {\\n\\\n\tvec2 uv = v_coord;\\n\\\n\tvec3 color = vec3(0.0);\\n\\\n\t//your code here\\n\\\n\tcolor.xy=uv;\\n\\n\\\n\tgl_FragColor = vec4(color, 1.0);\\n\\\n}\\n\\\n\";\n\n\tLiteGraph.registerNodeType(\"texture/shader\", LGraphTextureShader);\n\n\t// Texture Scale Offset\n\n\tfunction LGraphTextureScaleOffset() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"scale\", \"vec2\");\n\t\tthis.addInput(\"offset\", \"vec2\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\toffset: vec2.fromValues(0, 0),\n\t\t\tscale: vec2.fromValues(1, 1),\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureScaleOffset.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureScaleOffset.title = \"Scale/Offset\";\n\tLGraphTextureScaleOffset.desc = \"Applies an scaling and offseting\";\n\n\tLGraphTextureScaleOffset.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (!this.isOutputConnected(0) || !tex) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = tex.width;\n\t\tvar height = tex.height;\n\t\tvar type =  this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT;\n\t\tif (this.precision === LGraphTexture.DEFAULT) {\n\t\t\ttype = tex.type;\n\t\t}\n\n\t\tif (\n\t\t\t!this._tex ||\n\t\t\tthis._tex.width != width ||\n\t\t\tthis._tex.height != height ||\n\t\t\tthis._tex.type != type\n\t\t) {\n\t\t\tthis._tex = new GL.Texture(width, height, {\n\t\t\t\ttype: type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tvar shader = this._shader;\n\n\t\tif (!shader) {\n\t\t\tshader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureScaleOffset.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar scale = this.getInputData(1);\n\t\tif (scale) {\n\t\t\tthis.properties.scale[0] = scale[0];\n\t\t\tthis.properties.scale[1] = scale[1];\n\t\t} else {\n\t\t\tscale = this.properties.scale;\n\t\t}\n\n\t\tvar offset = this.getInputData(2);\n\t\tif (offset) {\n\t\t\tthis.properties.offset[0] = offset[0];\n\t\t\tthis.properties.offset[1] = offset[1];\n\t\t} else {\n\t\t\toffset = this.properties.offset;\n\t\t}\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\tgl.disable(gl.CULL_FACE);\n\t\t\tgl.disable(gl.BLEND);\n\t\t\ttex.bind(0);\n\t\t\tvar mesh = Mesh.getScreenQuad();\n\t\t\tshader\n\t\t\t\t.uniforms({\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tu_scale: scale,\n\t\t\t\t\tu_offset: offset\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureScaleOffset.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\t\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform vec2 u_scale;\\n\\\n\t\tuniform vec2 u_offset;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec2 uv = v_coord;\\n\\\n\t\t\tuv = uv / u_scale - u_offset;\\n\\\n\t\t\tgl_FragColor = texture2D(u_texture, uv);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/scaleOffset\",\n\t\tLGraphTextureScaleOffset\n\t);\n\n\t// Warp (distort a texture) *************************\n\n\tfunction LGraphTextureWarp() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"warp\", \"Texture\");\n\t\tthis.addInput(\"factor\", \"number\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tfactor: 0.01,\n\t\t\tscale: [1,1],\n\t\t\toffset: [0,0],\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\n\t\tthis._uniforms = { \n\t\t\tu_texture: 0, \n\t\t\tu_textureB: 1, \n\t\t\tu_factor: 1, \n\t\t\tu_scale: vec2.create(),\n\t\t\tu_offset: vec2.create()\n\t\t};\n\t}\n\n\tLGraphTextureWarp.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureWarp.title = \"Warp\";\n\tLGraphTextureWarp.desc = \"Texture warp operation\";\n\n\tLGraphTextureWarp.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar texB = this.getInputData(1);\n\n\t\tvar width = 512;\n\t\tvar height = 512;\n\t\tvar type = gl.UNSIGNED_BYTE;\n\t\tif (tex) {\n\t\t\twidth = tex.width;\n\t\t\theight = tex.height;\n\t\t\ttype = tex.type;\n\t\t} else if (texB) {\n\t\t\twidth = texB.width;\n\t\t\theight = texB.height;\n\t\t\ttype = texB.type;\n\t\t}\n\n\t\tif (!tex && !this._tex) {\n\t\t\tthis._tex = new GL.Texture(width, height, {\n\t\t\t\ttype:\n\t\t\t\t\tthis.precision === LGraphTexture.LOW\n\t\t\t\t\t\t? gl.UNSIGNED_BYTE\n\t\t\t\t\t\t: gl.HIGH_PRECISION_FORMAT,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t} else {\n\t\t\tthis._tex = LGraphTexture.getTargetTexture(\n\t\t\t\ttex || this._tex,\n\t\t\t\tthis._tex,\n\t\t\t\tthis.properties.precision\n\t\t\t);\n\t\t}\n\n\t\tvar shader = this._shader;\n\n\t\tif (!shader) {\n\t\t\tshader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureWarp.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar factor = this.getInputData(2);\n\t\tif (factor != null) {\n\t\t\tthis.properties.factor = factor;\n\t\t} else {\n\t\t\tfactor = parseFloat(this.properties.factor);\n\t\t}\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_factor = factor;\n\t\tuniforms.u_scale.set( this.properties.scale );\n\t\tuniforms.u_offset.set( this.properties.offset );\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\tgl.disable(gl.CULL_FACE);\n\t\t\tgl.disable(gl.BLEND);\n\t\t\tif (tex) {\n\t\t\t\ttex.bind(0);\n\t\t\t}\n\t\t\tif (texB) {\n\t\t\t\ttexB.bind(1);\n\t\t\t}\n\t\t\tvar mesh = Mesh.getScreenQuad();\n\t\t\tshader\n\t\t\t\t.uniforms( uniforms )\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureWarp.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\t\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform float u_factor;\\n\\\n\t\tuniform vec2 u_scale;\\n\\\n\t\tuniform vec2 u_offset;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec2 uv = v_coord;\\n\\\n\t\t\tuv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor * u_scale + u_offset;\\n\\\n\t\t\tgl_FragColor = texture2D(u_texture, uv);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/warp\", LGraphTextureWarp);\n\n\t//****************************************************\n\n\t// Texture to Viewport *****************************************\n\tfunction LGraphTextureToViewport() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tadditive: false,\n\t\t\tantialiasing: false,\n\t\t\tfilter: true,\n\t\t\tdisable_alpha: false,\n\t\t\tgamma: 1.0,\n\t\t\tviewport: [0,0,1,1]\n\t\t};\n\t\tthis.size[0] = 130;\n\t}\n\n\tLGraphTextureToViewport.title = \"to Viewport\";\n\tLGraphTextureToViewport.desc = \"Texture to viewport\";\n\n\tLGraphTextureToViewport._prev_viewport = new Float32Array(4);\n\n\tLGraphTextureToViewport.prototype.onDrawBackground = function( ctx )\n\t{\n\t\tif ( this.flags.collapsed || this.size[1] <= 40 )\n\t\t\treturn;\n\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tctx.drawImage( ctx == gl ? tex : gl.canvas, 10,30, this.size[0] -20, this.size[1] -40);\n\t}\n\n\tLGraphTextureToViewport.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.properties.disable_alpha) {\n\t\t\tgl.disable(gl.BLEND);\n\t\t} else {\n\t\t\tgl.enable(gl.BLEND);\n\t\t\tif (this.properties.additive) {\n\t\t\t\tgl.blendFunc(gl.SRC_ALPHA, gl.ONE);\n\t\t\t} else {\n\t\t\t\tgl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n\t\t\t}\n\t\t}\n\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tvar gamma = this.properties.gamma || 1.0;\n\t\tif (this.isInputConnected(1)) {\n\t\t\tgamma = this.getInputData(1);\n\t\t}\n\n\t\ttex.setParameter(\n\t\t\tgl.TEXTURE_MAG_FILTER,\n\t\t\tthis.properties.filter ? gl.LINEAR : gl.NEAREST\n\t\t);\n\n\t\tvar old_viewport = LGraphTextureToViewport._prev_viewport;\n\t\told_viewport.set( gl.viewport_data );\n\t\tvar new_view = this.properties.viewport;\n\t\tgl.viewport( old_viewport[0] + old_viewport[2] * new_view[0], old_viewport[1] + old_viewport[3] * new_view[1], old_viewport[2] * new_view[2], old_viewport[3] * new_view[3] );\n\t\tvar viewport = gl.getViewport(); //gl.getParameter(gl.VIEWPORT);\n\n\t\tif (this.properties.antialiasing) {\n\t\t\tif (!LGraphTextureToViewport._shader) {\n\t\t\t\tLGraphTextureToViewport._shader = new GL.Shader(\n\t\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\tLGraphTextureToViewport.aa_pixel_shader\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tvar mesh = Mesh.getScreenQuad();\n\t\t\ttex.bind(0);\n\t\t\tLGraphTextureToViewport._shader\n\t\t\t\t.uniforms({\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tuViewportSize: [tex.width, tex.height],\n\t\t\t\t\tu_igamma: 1 / gamma,\n\t\t\t\t\tinverseVP: [1 / tex.width, 1 / tex.height]\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t} else {\n\t\t\tif (gamma != 1.0) {\n\t\t\t\tif (!LGraphTextureToViewport._gamma_shader) {\n\t\t\t\t\tLGraphTextureToViewport._gamma_shader = new GL.Shader(\n\t\t\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\t\tLGraphTextureToViewport.gamma_pixel_shader\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\ttex.toViewport(LGraphTextureToViewport._gamma_shader, {\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tu_igamma: 1 / gamma\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\ttex.toViewport();\n\t\t\t}\n\t\t}\n\n\t\tgl.viewport( old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3] );\n\t};\n\n\tLGraphTextureToViewport.prototype.onGetInputs = function() {\n\t\treturn [[\"gamma\", \"number\"]];\n\t};\n\n\tLGraphTextureToViewport.aa_pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 uViewportSize;\\n\\\n\t\tuniform vec2 inverseVP;\\n\\\n\t\tuniform float u_igamma;\\n\\\n\t\t#define FXAA_REDUCE_MIN   (1.0/ 128.0)\\n\\\n\t\t#define FXAA_REDUCE_MUL   (1.0 / 8.0)\\n\\\n\t\t#define FXAA_SPAN_MAX     8.0\\n\\\n\t\t\\n\\\n\t\t/* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\\n\\\n\t\tvec4 applyFXAA(sampler2D tex, vec2 fragCoord)\\n\\\n\t\t{\\n\\\n\t\t\tvec4 color = vec4(0.0);\\n\\\n\t\t\t/*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\\n\\\n\t\t\tvec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\\n\\\n\t\t\tvec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\\n\\\n\t\t\tvec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\\n\\\n\t\t\tvec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\\n\\\n\t\t\tvec3 rgbM  = texture2D(tex, fragCoord  * inverseVP).xyz;\\n\\\n\t\t\tvec3 luma = vec3(0.299, 0.587, 0.114);\\n\\\n\t\t\tfloat lumaNW = dot(rgbNW, luma);\\n\\\n\t\t\tfloat lumaNE = dot(rgbNE, luma);\\n\\\n\t\t\tfloat lumaSW = dot(rgbSW, luma);\\n\\\n\t\t\tfloat lumaSE = dot(rgbSE, luma);\\n\\\n\t\t\tfloat lumaM  = dot(rgbM,  luma);\\n\\\n\t\t\tfloat lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\\n\\\n\t\t\tfloat lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\\n\\\n\t\t\t\\n\\\n\t\t\tvec2 dir;\\n\\\n\t\t\tdir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\\n\\\n\t\t\tdir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));\\n\\\n\t\t\t\\n\\\n\t\t\tfloat dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\\n\\\n\t\t\t\\n\\\n\t\t\tfloat rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\\n\\\n\t\t\tdir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\\n\\\n\t\t\t\\n\\\n\t\t\tvec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \\n\\\n\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\\n\\\n\t\t\tvec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \\n\\\n\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\\n\\\n\t\t\t\\n\\\n\t\t\t//return vec4(rgbA,1.0);\\n\\\n\t\t\tfloat lumaB = dot(rgbB, luma);\\n\\\n\t\t\tif ((lumaB < lumaMin) || (lumaB > lumaMax))\\n\\\n\t\t\t\tcolor = vec4(rgbA, 1.0);\\n\\\n\t\t\telse\\n\\\n\t\t\t\tcolor = vec4(rgbB, 1.0);\\n\\\n\t\t\tif(u_igamma != 1.0)\\n\\\n\t\t\t\tcolor.xyz = pow( color.xyz, vec3(u_igamma) );\\n\\\n\t\t\treturn color;\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t   gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLGraphTextureToViewport.gamma_pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_igamma;\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = texture2D( u_texture, v_coord);\\n\\\n\t\t\tcolor.xyz = pow(color.xyz, vec3(u_igamma) );\\n\\\n\t\t   gl_FragColor = color;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/toviewport\",\n\t\tLGraphTextureToViewport\n\t);\n\n\t// Texture Copy *****************************************\n\tfunction LGraphTextureCopy() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tsize: 0,\n\t\t\tgenerate_mipmaps: false,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureCopy.title = \"Copy\";\n\tLGraphTextureCopy.desc = \"Copy Texture\";\n\tLGraphTextureCopy.widgets_info = {\n\t\tsize: {\n\t\t\twidget: \"combo\",\n\t\t\tvalues: [0, 32, 64, 128, 256, 512, 1024, 2048]\n\t\t},\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureCopy.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex && !this._temp_texture) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\t//copy the texture\n\t\tif (tex) {\n\t\t\tvar width = tex.width;\n\t\t\tvar height = tex.height;\n\n\t\t\tif (this.properties.size != 0) {\n\t\t\t\twidth = this.properties.size;\n\t\t\t\theight = this.properties.size;\n\t\t\t}\n\n\t\t\tvar temp = this._temp_texture;\n\n\t\t\tvar type = tex.type;\n\t\t\tif (this.properties.precision === LGraphTexture.LOW) {\n\t\t\t\ttype = gl.UNSIGNED_BYTE;\n\t\t\t} else if (this.properties.precision === LGraphTexture.HIGH) {\n\t\t\t\ttype = gl.HIGH_PRECISION_FORMAT;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\t!temp ||\n\t\t\t\ttemp.width != width ||\n\t\t\t\ttemp.height != height ||\n\t\t\t\ttemp.type != type\n\t\t\t) {\n\t\t\t\tvar minFilter = gl.LINEAR;\n\t\t\t\tif (\n\t\t\t\t\tthis.properties.generate_mipmaps &&\n\t\t\t\t\tisPowerOfTwo(width) &&\n\t\t\t\t\tisPowerOfTwo(height)\n\t\t\t\t) {\n\t\t\t\t\tminFilter = gl.LINEAR_MIPMAP_LINEAR;\n\t\t\t\t}\n\t\t\t\tthis._temp_texture = new GL.Texture(width, height, {\n\t\t\t\t\ttype: type,\n\t\t\t\t\tformat: gl.RGBA,\n\t\t\t\t\tminFilter: minFilter,\n\t\t\t\t\tmagFilter: gl.LINEAR\n\t\t\t\t});\n\t\t\t}\n\t\t\ttex.copyTo(this._temp_texture);\n\n\t\t\tif (this.properties.generate_mipmaps) {\n\t\t\t\tthis._temp_texture.bind(0);\n\t\t\t\tgl.generateMipmap(this._temp_texture.texture_type);\n\t\t\t\tthis._temp_texture.unbind(0);\n\t\t\t}\n\t\t}\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/copy\", LGraphTextureCopy);\n\n\t// Texture Downsample *****************************************\n\tfunction LGraphTextureDownsample() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = {\n\t\t\titerations: 1,\n\t\t\tgenerate_mipmaps: false,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureDownsample.title = \"Downsample\";\n\tLGraphTextureDownsample.desc = \"Downsample Texture\";\n\tLGraphTextureDownsample.widgets_info = {\n\t\titerations: { type: \"number\", step: 1, precision: 0, min: 0 },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureDownsample.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex && !this._temp_texture) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\t//we do not allow any texture different than texture 2D\n\t\tif (!tex || tex.texture_type !== GL.TEXTURE_2D) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.properties.iterations < 1) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar shader = LGraphTextureDownsample._shader;\n\t\tif (!shader) {\n\t\t\tLGraphTextureDownsample._shader = shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureDownsample.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar width = tex.width | 0;\n\t\tvar height = tex.height | 0;\n\t\tvar type = tex.type;\n\t\tif (this.properties.precision === LGraphTexture.LOW) {\n\t\t\ttype = gl.UNSIGNED_BYTE;\n\t\t} else if (this.properties.precision === LGraphTexture.HIGH) {\n\t\t\ttype = gl.HIGH_PRECISION_FORMAT;\n\t\t}\n\t\tvar iterations = this.properties.iterations || 1;\n\n\t\tvar origin = tex;\n\t\tvar target = null;\n\n\t\tvar temp = [];\n\t\tvar options = {\n\t\t\ttype: type,\n\t\t\tformat: tex.format\n\t\t};\n\n\t\tvar offset = vec2.create();\n\t\tvar uniforms = {\n\t\t\tu_offset: offset\n\t\t};\n\n\t\tif (this._texture) {\n\t\t\tGL.Texture.releaseTemporary(this._texture);\n\t\t}\n\n\t\tfor (var i = 0; i < iterations; ++i) {\n\t\t\toffset[0] = 1 / width;\n\t\t\toffset[1] = 1 / height;\n\t\t\twidth = width >> 1 || 0;\n\t\t\theight = height >> 1 || 0;\n\t\t\ttarget = GL.Texture.getTemporary(width, height, options);\n\t\t\ttemp.push(target);\n\t\t\torigin.setParameter(GL.TEXTURE_MAG_FILTER, GL.NEAREST);\n\t\t\torigin.copyTo(target, shader, uniforms);\n\t\t\tif (width == 1 && height == 1) {\n\t\t\t\tbreak;\n\t\t\t} //nothing else to do\n\t\t\torigin = target;\n\t\t}\n\n\t\t//keep the last texture used\n\t\tthis._texture = temp.pop();\n\n\t\t//free the rest\n\t\tfor (var i = 0; i < temp.length; ++i) {\n\t\t\tGL.Texture.releaseTemporary(temp[i]);\n\t\t}\n\n\t\tif (this.properties.generate_mipmaps) {\n\t\t\tthis._texture.bind(0);\n\t\t\tgl.generateMipmap(this._texture.texture_type);\n\t\t\tthis._texture.unbind(0);\n\t\t}\n\n\t\tthis.setOutputData(0, this._texture);\n\t};\n\n\tLGraphTextureDownsample.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 u_offset;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = texture2D(u_texture, v_coord );\\n\\\n\t\t\tcolor += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\\n\\\n\t\t\tcolor += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\\n\\\n\t\t\tcolor += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\\n\\\n\t\t   gl_FragColor = color * 0.25;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/downsample\",\n\t\tLGraphTextureDownsample\n\t);\n\n\n\n\tfunction LGraphTextureResize() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tsize: [512,512],\n\t\t\tgenerate_mipmaps: false,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureResize.title = \"Resize\";\n\tLGraphTextureResize.desc = \"Resize Texture\";\n\tLGraphTextureResize.widgets_info = {\n\t\titerations: { type: \"number\", step: 1, precision: 0, min: 0 },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureResize.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex && !this._temp_texture) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\t//we do not allow any texture different than texture 2D\n\t\tif (!tex || tex.texture_type !== GL.TEXTURE_2D) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = this.properties.size[0] | 0;\n\t\tvar height = this.properties.size[1] | 0;\n\t\tif(width == 0)\n\t\t\twidth = tex.width;\n\t\tif(height == 0)\n\t\t\theight = tex.height;\n\t\tvar type = tex.type;\n\t\tif (this.properties.precision === LGraphTexture.LOW) {\n\t\t\ttype = gl.UNSIGNED_BYTE;\n\t\t} else if (this.properties.precision === LGraphTexture.HIGH) {\n\t\t\ttype = gl.HIGH_PRECISION_FORMAT;\n\t\t}\n\n\t\tif( !this._texture || this._texture.width != width || this._texture.height != height || this._texture.type != type )\n\t\t\tthis._texture = new GL.Texture( width, height, { type: type } );\n\n\t\ttex.copyTo( this._texture );\n\n\t\tif (this.properties.generate_mipmaps) {\n\t\t\tthis._texture.bind(0);\n\t\t\tgl.generateMipmap(this._texture.texture_type);\n\t\t\tthis._texture.unbind(0);\n\t\t}\n\n\t\tthis.setOutputData(0, this._texture);\n\t};\n\n\tLiteGraph.registerNodeType( \"texture/resize\", LGraphTextureResize );\n\n\t// Texture Average  *****************************************\n\tfunction LGraphTextureAverage() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"tex\", \"Texture\");\n\t\tthis.addOutput(\"avg\", \"vec4\");\n\t\tthis.addOutput(\"lum\", \"number\");\n\t\tthis.properties = {\n\t\t\tuse_previous_frame: true, //to avoid stalls \n\t\t\thigh_quality: false //to use as much pixels as possible\n\t\t};\n\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_mipmap_offset: 0\n\t\t};\n\t\tthis._luminance = new Float32Array(4);\n\t}\n\n\tLGraphTextureAverage.title = \"Average\";\n\tLGraphTextureAverage.desc =\n\t\t\"Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture.\\n If high_quality is true, then it generates the mipmaps first and reads from the lower one.\";\n\n\tLGraphTextureAverage.prototype.onExecute = function() {\n\t\tif (!this.properties.use_previous_frame) {\n\t\t\tthis.updateAverage();\n\t\t}\n\n\t\tvar v = this._luminance;\n\t\tthis.setOutputData(0, this._temp_texture);\n\t\tthis.setOutputData(1, v);\n\t\tthis.setOutputData(2, (v[0] + v[1] + v[2]) / 3);\n\t};\n\n\t//executed before rendering the frame\n\tLGraphTextureAverage.prototype.onPreRenderExecute = function() {\n\t\tthis.updateAverage();\n\t};\n\n\tLGraphTextureAverage.prototype.updateAverage = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\t!this.isOutputConnected(0) &&\n\t\t\t!this.isOutputConnected(1) &&\n\t\t\t!this.isOutputConnected(2)\n\t\t) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (!LGraphTextureAverage._shader) {\n\t\t\tLGraphTextureAverage._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureAverage.pixel_shader\n\t\t\t);\n\t\t\t//creates 256 random numbers and stores them in two mat4\n\t\t\tvar samples = new Float32Array(16);\n\t\t\tfor (var i = 0; i < samples.length; ++i) {\n\t\t\t\tsamples[i] = Math.random(); //poorly distributed samples\n\t\t\t}\n\t\t\t//upload only once\n\t\t\tLGraphTextureAverage._shader.uniforms({\n\t\t\t\tu_samples_a: samples.subarray(0, 16),\n\t\t\t\tu_samples_b: samples.subarray(16, 32)\n\t\t\t});\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\t\tvar type = gl.UNSIGNED_BYTE;\n\t\tif (tex.type != type) {\n\t\t\t//force floats, half floats cannot be read with gl.readPixels\n\t\t\ttype = gl.FLOAT;\n\t\t}\n\n\t\tif (!temp || temp.type != type) {\n\t\t\tthis._temp_texture = new GL.Texture(1, 1, {\n\t\t\t\ttype: type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.NEAREST\n\t\t\t});\n\t\t}\n\n\t\tthis._uniforms.u_mipmap_offset = 0;\n\n\t\tif(this.properties.high_quality)\n\t\t{\n\t\t\tif( !this._temp_pot2_texture || this._temp_pot2_texture.type != type )\n\t\t\t\tthis._temp_pot2_texture = new GL.Texture(512, 512, {\n\t\t\t\t\ttype: type,\n\t\t\t\t\tformat: gl.RGBA,\n\t\t\t\t\tminFilter: gl.LINEAR_MIPMAP_LINEAR,\n\t\t\t\t\tmagFilter: gl.LINEAR\n\t\t\t\t});\n\n\t\t\ttex.copyTo( this._temp_pot2_texture );\n\t\t\ttex = this._temp_pot2_texture;\n\t\t\ttex.bind(0);\n\t\t\tgl.generateMipmap(GL.TEXTURE_2D);\n\t\t\tthis._uniforms.u_mipmap_offset = 9;\n\t\t}\n\n\t\tvar shader = LGraphTextureAverage._shader;\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_mipmap_offset = this.properties.mipmap_offset;\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tgl.disable(gl.BLEND);\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\ttex.toViewport(shader, uniforms);\n\t\t});\n\n\t\tif (this.isOutputConnected(1) || this.isOutputConnected(2)) {\n\t\t\tvar pixel = this._temp_texture.getPixels();\n\t\t\tif (pixel) {\n\t\t\t\tvar v = this._luminance;\n\t\t\t\tvar type = this._temp_texture.type;\n\t\t\t\tv.set(pixel);\n\t\t\t\tif (type == gl.UNSIGNED_BYTE) {\n\t\t\t\t\tvec4.scale(v, v, 1 / 255);\n\t\t\t\t} else if (\n\t\t\t\t\ttype == GL.HALF_FLOAT ||\n\t\t\t\t\ttype == GL.HALF_FLOAT_OES\n\t\t\t\t) {\n\t\t\t\t\t//no half floats possible, hard to read back unless copyed to a FLOAT texture, so temp_texture is always forced to FLOAT\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tLGraphTextureAverage.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform mat4 u_samples_a;\\n\\\n\t\tuniform mat4 u_samples_b;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_mipmap_offset;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = vec4(0.0);\\n\\\n\t\t\t//random average\\n\\\n\t\t\tfor(int i = 0; i < 4; ++i)\\n\\\n\t\t\t\tfor(int j = 0; j < 4; ++j)\\n\\\n\t\t\t\t{\\n\\\n\t\t\t\t\tcolor += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\\n\\\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\\\n\t\t\t\t}\\n\\\n\t\t   gl_FragColor = color * 0.03125;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/average\", LGraphTextureAverage);\n\n\n\n\t// Computes operation between pixels (max, min)  *****************************************\n\tfunction LGraphTextureMinMax() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"min_t\", \"Texture\");\n\t\tthis.addOutput(\"max_t\", \"Texture\");\n\t\tthis.addOutput(\"min\", \"vec4\");\n\t\tthis.addOutput(\"max\", \"vec4\");\n\t\tthis.properties = {\n\t\t\tmode: \"max\",\n\t\t\tuse_previous_frame: true //to avoid stalls \n\t\t};\n\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0\n\t\t};\n\n\t\tthis._max = new Float32Array(4);\n\t\tthis._min = new Float32Array(4);\n\n\t\tthis._textures_chain = [];\n\t}\n\n\tLGraphTextureMinMax.widgets_info = {\n\t\tmode: { widget: \"combo\", values: [\"min\",\"max\",\"avg\"] }\n\t};\n\n\tLGraphTextureMinMax.title = \"MinMax\";\n\tLGraphTextureMinMax.desc = \"Compute the scene min max\";\n\n\tLGraphTextureMinMax.prototype.onExecute = function() {\n\t\tif (!this.properties.use_previous_frame) {\n\t\t\tthis.update();\n\t\t}\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t\tthis.setOutputData(1, this._luminance);\n\t};\n\n\t//executed before rendering the frame\n\tLGraphTextureMinMax.prototype.onPreRenderExecute = function() {\n\t\tthis.update();\n\t};\n\n\tLGraphTextureMinMax.prototype.update = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !this.isOutputConnected(0) && !this.isOutputConnected(1) ) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (!LGraphTextureMinMax._shader) {\n\t\t\tLGraphTextureMinMax._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureMinMax.pixel_shader );\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\t\tvar type = gl.UNSIGNED_BYTE;\n\t\tif (tex.type != type) {\n\t\t\t//force floats, half floats cannot be read with gl.readPixels\n\t\t\ttype = gl.FLOAT;\n\t\t}\n\n\t\tvar size = 512;\n\n\t\tif( !this._textures_chain.length || this._textures_chain[0].type != type )\n\t\t{\n\t\t\tvar index = 0;\n\t\t\twhile(i)\n\t\t\t{\n\t\t\t\tthis._textures_chain[i] = new GL.Texture( size, size, {\n\t\t\t\t\ttype: type,\n\t\t\t\t\tformat: gl.RGBA,\n\t\t\t\t\tfilter: gl.NEAREST\n\t\t\t\t});\n\t\t\t\tsize = size >> 2;\n\t\t\t\ti++;\n\t\t\t\tif(size == 1)\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\ttex.copyTo( this._textures_chain[0] );\n\t\tvar prev = this._textures_chain[0];\n\t\tfor(var i = 1; i <= this._textures_chain.length; ++i)\n\t\t{\n\t\t\tvar tex = this._textures_chain[i];\n\n\t\t\tprev = tex;\t\t\t\t\n\t\t}\n\n\t\tvar shader = LGraphTextureMinMax._shader;\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_mipmap_offset = this.properties.mipmap_offset;\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tgl.disable(gl.BLEND);\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\ttex.toViewport(shader, uniforms);\n\t\t});\n\t};\n\n\tLGraphTextureMinMax.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform mat4 u_samples_a;\\n\\\n\t\tuniform mat4 u_samples_b;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_mipmap_offset;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = vec4(0.0);\\n\\\n\t\t\t//random average\\n\\\n\t\t\tfor(int i = 0; i < 4; ++i)\\n\\\n\t\t\t\tfor(int j = 0; j < 4; ++j)\\n\\\n\t\t\t\t{\\n\\\n\t\t\t\t\tcolor += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\\n\\\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\\\n\t\t\t\t}\\n\\\n\t\t   gl_FragColor = color * 0.03125;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\t//LiteGraph.registerNodeType(\"texture/clustered_operation\", LGraphTextureClusteredOperation);\n\n\n\tfunction LGraphTextureTemporalSmooth() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"factor\", \"Number\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = { factor: 0.5 };\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_textureB: 1,\n\t\t\tu_factor: this.properties.factor\n\t\t};\n\t}\n\n\tLGraphTextureTemporalSmooth.title = \"Smooth\";\n\tLGraphTextureTemporalSmooth.desc = \"Smooth texture over time\";\n\n\tLGraphTextureTemporalSmooth.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex || !this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!LGraphTextureTemporalSmooth._shader) {\n\t\t\tLGraphTextureTemporalSmooth._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureTemporalSmooth.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.type != tex.type ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height\n\t\t) {\n\t\t\tvar options = {\n\t\t\t\ttype: tex.type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.NEAREST\n\t\t\t};\n\t\t\tthis._temp_texture = new GL.Texture(tex.width, tex.height, options );\n\t\t\tthis._temp_texture2 = new GL.Texture(tex.width, tex.height, options );\n\t\t\ttex.copyTo(this._temp_texture2);\n\t\t}\n\n\t\tvar tempA = this._temp_texture;\n\t\tvar tempB = this._temp_texture2;\n\n\t\tvar shader = LGraphTextureTemporalSmooth._shader;\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_factor = 1.0 - this.getInputOrProperty(\"factor\");\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\ttempA.drawTo(function() {\n\t\t\ttempB.bind(1);\n\t\t\ttex.toViewport(shader, uniforms);\n\t\t});\n\n\t\tthis.setOutputData(0, tempA);\n\n\t\t//swap\n\t\tthis._temp_texture = tempB;\n\t\tthis._temp_texture2 = tempA;\n\t};\n\n\tLGraphTextureTemporalSmooth.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tuniform float u_factor;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tgl_FragColor = mix( texture2D( u_texture, v_coord ), texture2D( u_textureB, v_coord ), u_factor );\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType( \"texture/temporal_smooth\", LGraphTextureTemporalSmooth );\n\n\n\tfunction LGraphTextureLinearAvgSmooth() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addOutput(\"avg\", \"Texture\");\n\t\tthis.addOutput(\"array\", \"Texture\");\n\t\tthis.properties = { samples: 64, frames_interval: 1 };\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_textureB: 1,\n\t\t\tu_samples: this.properties.samples,\n\t\t\tu_isamples: 1/this.properties.samples\n\t\t};\n\t\tthis.frame = 0;\n\t}\n\n\tLGraphTextureLinearAvgSmooth.title = \"Lineal Avg Smooth\";\n\tLGraphTextureLinearAvgSmooth.desc = \"Smooth texture linearly over time\";\n\n\tLGraphTextureLinearAvgSmooth[\"@samples\"] = { type: \"number\", min: 1, max: 64, step: 1, precision: 1 };\n\n\tLGraphTextureLinearAvgSmooth.prototype.getPreviewTexture = function()\n\t{\n\t\treturn this._temp_texture2;\n\t}\n\n\tLGraphTextureLinearAvgSmooth.prototype.onExecute = function() {\n\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex || !this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!LGraphTextureLinearAvgSmooth._shader) {\n\t\t\tLGraphTextureLinearAvgSmooth._shader_copy = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearAvgSmooth.pixel_shader_copy );\n\t\t\tLGraphTextureLinearAvgSmooth._shader_avg = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearAvgSmooth.pixel_shader_avg );\n\t\t}\n\n\t\tvar samples = clamp(this.properties.samples,0,64);\n\t\tvar frame = this.frame;\n\t\tvar interval = this.properties.frames_interval;\n\n\t\tif( interval == 0 || frame % interval == 0 )\n\t\t{\n\t\t\tvar temp = this._temp_texture;\n\t\t\tif ( !temp || temp.type != tex.type || temp.width != samples ) {\n\t\t\t\tvar options = {\n\t\t\t\t\ttype: tex.type,\n\t\t\t\t\tformat: gl.RGBA,\n\t\t\t\t\tfilter: gl.NEAREST\n\t\t\t\t};\n\t\t\t\tthis._temp_texture = new GL.Texture( samples, 1, options );\n\t\t\t\tthis._temp_texture2 = new GL.Texture( samples, 1, options );\n\t\t\t\tthis._temp_texture_out = new GL.Texture( 1, 1, options );\n\t\t\t}\n\n\t\t\tvar tempA = this._temp_texture;\n\t\t\tvar tempB = this._temp_texture2;\n\n\t\t\tvar shader_copy = LGraphTextureLinearAvgSmooth._shader_copy;\n\t\t\tvar shader_avg = LGraphTextureLinearAvgSmooth._shader_avg;\n\t\t\tvar uniforms = this._uniforms;\n\t\t\tuniforms.u_samples = samples;\n\t\t\tuniforms.u_isamples = 1.0 / samples;\n\n\t\t\tgl.disable(gl.BLEND);\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\ttempA.drawTo(function() {\n\t\t\t\ttempB.bind(1);\n\t\t\t\ttex.toViewport( shader_copy, uniforms );\n\t\t\t});\n\n\t\t\tthis._temp_texture_out.drawTo(function() {\n\t\t\t\ttempA.toViewport( shader_avg, uniforms );\n\t\t\t});\n\n\t\t\tthis.setOutputData( 0, this._temp_texture_out );\n\n\t\t\t//swap\n\t\t\tthis._temp_texture = tempB;\n\t\t\tthis._temp_texture2 = tempA;\n\t\t}\n\t\telse\n\t\t\tthis.setOutputData(0, this._temp_texture_out);\n\t\tthis.setOutputData(1, this._temp_texture2);\n\t\tthis.frame++;\n\t};\n\n\tLGraphTextureLinearAvgSmooth.pixel_shader_copy =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tuniform float u_isamples;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tif( v_coord.x <= u_isamples )\\n\\\n\t\t\t\tgl_FragColor = texture2D( u_texture, vec2(0.5) );\\n\\\n\t\t\telse\\n\\\n\t\t\t\tgl_FragColor = texture2D( u_textureB, v_coord - vec2(u_isamples,0.0) );\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLGraphTextureLinearAvgSmooth.pixel_shader_avg =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform int u_samples;\\n\\\n\t\tuniform float u_isamples;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = vec4(0.0);\\n\\\n\t\t\tfor(int i = 0; i < 64; ++i)\\n\\\n\t\t\t{\\n\\\n\t\t\t\tcolor += texture2D( u_texture, vec2( float(i)*u_isamples,0.0) );\\n\\\n\t\t\t\tif(i == (u_samples - 1))\\n\\\n\t\t\t\t\tbreak;\\n\\\n\t\t\t}\\n\\\n\t\t\tgl_FragColor = color * u_isamples;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\n\tLiteGraph.registerNodeType( \"texture/linear_avg_smooth\", LGraphTextureLinearAvgSmooth );\n\n\t// Image To Texture *****************************************\n\tfunction LGraphImageToTexture() {\n\t\tthis.addInput(\"Image\", \"image\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = {};\n\t}\n\n\tLGraphImageToTexture.title = \"Image to Texture\";\n\tLGraphImageToTexture.desc = \"Uploads an image to the GPU\";\n\t//LGraphImageToTexture.widgets_info = { size: { widget:\"combo\", values:[0,32,64,128,256,512,1024,2048]} };\n\n\tLGraphImageToTexture.prototype.onExecute = function() {\n\t\tvar img = this.getInputData(0);\n\t\tif (!img) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = img.videoWidth || img.width;\n\t\tvar height = img.videoHeight || img.height;\n\n\t\t//this is in case we are using a webgl canvas already, no need to reupload it\n\t\tif (img.gltexture) {\n\t\t\tthis.setOutputData(0, img.gltexture);\n\t\t\treturn;\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\t\tif (!temp || temp.width != width || temp.height != height) {\n\t\t\tthis._temp_texture = new GL.Texture(width, height, {\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\ttry {\n\t\t\tthis._temp_texture.uploadImage(img);\n\t\t} catch (err) {\n\t\t\tconsole.error(\n\t\t\t\t\"image comes from an unsafe location, cannot be uploaded to webgl: \" +\n\t\t\t\t\terr\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/imageToTexture\",\n\t\tLGraphImageToTexture\n\t);\n\n\t// Texture LUT *****************************************\n\tfunction LGraphTextureLUT() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addInput(\"LUT\", \"Texture\");\n\t\tthis.addInput(\"Intensity\", \"number\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = { enabled: true, intensity: 1, precision: LGraphTexture.DEFAULT, texture: null };\n\n\t\tif (!LGraphTextureLUT._shader) {\n\t\t\tLGraphTextureLUT._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureLUT.pixel_shader );\n\t\t}\n\t}\n\n\tLGraphTextureLUT.widgets_info = {\n\t\ttexture: { widget: \"texture\" },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureLUT.title = \"LUT\";\n\tLGraphTextureLUT.desc = \"Apply LUT to Texture\";\n\n\tLGraphTextureLUT.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar lut_tex = this.getInputData(1);\n\n\t\tif (!lut_tex) {\n\t\t\tlut_tex = LGraphTexture.getTexture(this.properties.texture);\n\t\t}\n\n\t\tif (!lut_tex) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tlut_tex.bind(0);\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n\t\tgl.texParameteri(\n\t\t\tgl.TEXTURE_2D,\n\t\t\tgl.TEXTURE_WRAP_S,\n\t\t\tgl.CLAMP_TO_EDGE\n\t\t);\n\t\tgl.texParameteri(\n\t\t\tgl.TEXTURE_2D,\n\t\t\tgl.TEXTURE_WRAP_T,\n\t\t\tgl.CLAMP_TO_EDGE\n\t\t);\n\t\tgl.bindTexture(gl.TEXTURE_2D, null);\n\n\t\tvar intensity = this.properties.intensity;\n\t\tif (this.isInputConnected(2)) {\n\t\t\tthis.properties.intensity = intensity = this.getInputData(2);\n\t\t}\n\n\t\tthis._tex = LGraphTexture.getTargetTexture(\n\t\t\ttex,\n\t\t\tthis._tex,\n\t\t\tthis.properties.precision\n\t\t);\n\n\t\t//var mesh = Mesh.getScreenQuad();\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tlut_tex.bind(1);\n\t\t\ttex.toViewport(LGraphTextureLUT._shader, {\n\t\t\t\tu_texture: 0,\n\t\t\t\tu_textureB: 1,\n\t\t\t\tu_amount: intensity\n\t\t\t});\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureLUT.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tuniform float u_amount;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\t lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\\n\\\n\t\t\t mediump float blueColor = textureColor.b * 63.0;\\n\\\n\t\t\t mediump vec2 quad1;\\n\\\n\t\t\t quad1.y = floor(floor(blueColor) / 8.0);\\n\\\n\t\t\t quad1.x = floor(blueColor) - (quad1.y * 8.0);\\n\\\n\t\t\t mediump vec2 quad2;\\n\\\n\t\t\t quad2.y = floor(ceil(blueColor) / 8.0);\\n\\\n\t\t\t quad2.x = ceil(blueColor) - (quad2.y * 8.0);\\n\\\n\t\t\t highp vec2 texPos1;\\n\\\n\t\t\t texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\\n\\\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\\\n\t\t\t highp vec2 texPos2;\\n\\\n\t\t\t texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\\n\\\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\\\n\t\t\t lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\\n\\\n\t\t\t lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\\n\\\n\t\t\t lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\\n\\\n\t\t\t gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/LUT\", LGraphTextureLUT);\n\n\n\t// Texture LUT *****************************************\n\tfunction LGraphTextureEncode() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addInput(\"Atlas\", \"Texture\");\n\t\tthis.addOutput(\"\", \"Texture\");\n\t\tthis.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, generate_mipmaps: false, texture: null };\n\n\t\tif (!LGraphTextureEncode._shader) {\n\t\t\tLGraphTextureEncode._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEncode.pixel_shader );\n\t\t}\n\n\t\tthis._uniforms = {\n\t\t\t\tu_texture: 0,\n\t\t\t\tu_textureB: 1,\n\t\t\t\tu_row_simbols: 4,\n\t\t\t\tu_simbol_size: 16,\n\t\t\t\tu_res: vec2.create()\n\t\t};\n\t}\n\n\tLGraphTextureEncode.widgets_info = {\n\t\ttexture: { widget: \"texture\" },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureEncode.title = \"Encode\";\n\tLGraphTextureEncode.desc = \"Apply a texture atlas to encode a texture\";\n\n\tLGraphTextureEncode.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar symbols_tex = this.getInputData(1);\n\n\t\tif (!symbols_tex) {\n\t\t\tsymbols_tex = LGraphTexture.getTexture(this.properties.texture);\n\t\t}\n\n\t\tif (!symbols_tex) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tsymbols_tex.bind(0);\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST );\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST );\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );\n\t\tgl.bindTexture(gl.TEXTURE_2D, null);\n\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_row_simbols = Math.floor(this.properties.num_row_symbols);\n\t\tuniforms.u_symbol_size = this.properties.symbol_size;\n\t\tuniforms.u_brightness = this.properties.brightness;\n\t\tuniforms.u_invert = this.properties.invert ? 1 : 0;\n\t\tuniforms.u_colorize = this.properties.colorize ? 1 : 0;\n\n\t\tthis._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );\n\t\tuniforms.u_res[0] = this._tex.width;\n\t\tuniforms.u_res[1] = this._tex.height;\n\t\tthis._tex.bind(0);\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );\n\t\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tsymbols_tex.bind(1);\n\t\t\ttex.toViewport(LGraphTextureEncode._shader, uniforms);\n\t\t});\n\n\t\tif (this.properties.generate_mipmaps) {\n\t\t\tthis._tex.bind(0);\n\t\t\tgl.generateMipmap(this._tex.texture_type);\n\t\t\tthis._tex.unbind(0);\n\t\t}\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureEncode.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tuniform float u_row_simbols;\\n\\\n\t\tuniform float u_symbol_size;\\n\\\n\t\tuniform float u_brightness;\\n\\\n\t\tuniform float u_invert;\\n\\\n\t\tuniform float u_colorize;\\n\\\n\t\tuniform vec2 u_res;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec2 total_symbols = u_res / u_symbol_size;\\n\\\n\t\t\tvec2 uv = floor(v_coord * total_symbols) / total_symbols; //pixelate \\n\\\n\t\t\tvec2 local_uv = mod(v_coord * u_res, u_symbol_size) / u_symbol_size;\\n\\\n\t\t\tlowp vec4 textureColor = texture2D(u_texture, uv );\\n\\\n\t\t\tfloat lum = clamp(u_brightness * (textureColor.x + textureColor.y + textureColor.z)/3.0,0.0,1.0);\\n\\\n\t\t\tif( u_invert == 1.0 ) lum = 1.0 - lum;\\n\\\n\t\t\tfloat index = floor( lum * (u_row_simbols * u_row_simbols - 1.0));\\n\\\n\t\t\tfloat col = mod( index, u_row_simbols );\\n\\\n\t\t\tfloat row = u_row_simbols - floor( index / u_row_simbols ) - 1.0;\\n\\\n\t\t\tvec2 simbol_uv = ( vec2( col, row ) + local_uv ) / u_row_simbols;\\n\\\n\t\t\tvec4 color = texture2D( u_textureB, simbol_uv );\\n\\\n\t\t\tif(u_colorize == 1.0)\\n\\\n\t\t\t\tcolor *= textureColor;\\n\\\n\t\t\tgl_FragColor = color;\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/encode\", LGraphTextureEncode);\n\n\t// Texture Channels *****************************************\n\tfunction LGraphTextureChannels() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\n\t\tthis.addOutput(\"R\", \"Texture\");\n\t\tthis.addOutput(\"G\", \"Texture\");\n\t\tthis.addOutput(\"B\", \"Texture\");\n\t\tthis.addOutput(\"A\", \"Texture\");\n\n\t\t//this.properties = { use_single_channel: true };\n\t\tif (!LGraphTextureChannels._shader) {\n\t\t\tLGraphTextureChannels._shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureChannels.pixel_shader\n\t\t\t);\n\t\t}\n\t}\n\n\tLGraphTextureChannels.title = \"Texture to Channels\";\n\tLGraphTextureChannels.desc = \"Split texture channels\";\n\n\tLGraphTextureChannels.prototype.onExecute = function() {\n\t\tvar texA = this.getInputData(0);\n\t\tif (!texA) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this._channels) {\n\t\t\tthis._channels = Array(4);\n\t\t}\n\n\t\t//var format = this.properties.use_single_channel ? gl.LUMINANCE : gl.RGBA; //not supported by WebGL1\n\t\tvar format = gl.RGB;\n\t\tvar connections = 0;\n\t\tfor (var i = 0; i < 4; i++) {\n\t\t\tif (this.isOutputConnected(i)) {\n\t\t\t\tif (\n\t\t\t\t\t!this._channels[i] ||\n\t\t\t\t\tthis._channels[i].width != texA.width ||\n\t\t\t\t\tthis._channels[i].height != texA.height ||\n\t\t\t\t\tthis._channels[i].type != texA.type ||\n\t\t\t\t\tthis._channels[i].format != format\n\t\t\t\t) {\n\t\t\t\t\tthis._channels[i] = new GL.Texture(\n\t\t\t\t\t\ttexA.width,\n\t\t\t\t\t\ttexA.height,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: texA.type,\n\t\t\t\t\t\t\tformat: format,\n\t\t\t\t\t\t\tfilter: gl.LINEAR\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconnections++;\n\t\t\t} else {\n\t\t\t\tthis._channels[i] = null;\n\t\t\t}\n\t\t}\n\n\t\tif (!connections) {\n\t\t\treturn;\n\t\t}\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tvar shader = LGraphTextureChannels._shader;\n\t\tvar masks = [\n\t\t\t[1, 0, 0, 0],\n\t\t\t[0, 1, 0, 0],\n\t\t\t[0, 0, 1, 0],\n\t\t\t[0, 0, 0, 1]\n\t\t];\n\n\t\tfor (var i = 0; i < 4; i++) {\n\t\t\tif (!this._channels[i]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis._channels[i].drawTo(function() {\n\t\t\t\ttexA.bind(0);\n\t\t\t\tshader\n\t\t\t\t\t.uniforms({ u_texture: 0, u_mask: masks[i] })\n\t\t\t\t\t.draw(mesh);\n\t\t\t});\n\t\t\tthis.setOutputData(i, this._channels[i]);\n\t\t}\n\t};\n\n\tLGraphTextureChannels.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec4 u_mask;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t   gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/textureChannels\",\n\t\tLGraphTextureChannels\n\t);\n\n\t// Texture Channels to Texture *****************************************\n\tfunction LGraphChannelsTexture() {\n\t\tthis.addInput(\"R\", \"Texture\");\n\t\tthis.addInput(\"G\", \"Texture\");\n\t\tthis.addInput(\"B\", \"Texture\");\n\t\tthis.addInput(\"A\", \"Texture\");\n\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\n\t\tthis.properties = {\n\t\t\tprecision: LGraphTexture.DEFAULT,\n\t\t\tR: 1,\n\t\t\tG: 1,\n\t\t\tB: 1,\n\t\t\tA: 1\n\t\t};\n\t\tthis._color = vec4.create();\n\t\tthis._uniforms = {\n\t\t\tu_textureR: 0,\n\t\t\tu_textureG: 1,\n\t\t\tu_textureB: 2,\n\t\t\tu_textureA: 3,\n\t\t\tu_color: this._color\n\t\t};\n\t}\n\n\tLGraphChannelsTexture.title = \"Channels to Texture\";\n\tLGraphChannelsTexture.desc = \"Split texture channels\";\n\tLGraphChannelsTexture.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphChannelsTexture.prototype.onExecute = function() {\n\t\tvar white = LGraphTexture.getWhiteTexture();\n\t\tvar texR = this.getInputData(0) || white;\n\t\tvar texG = this.getInputData(1) || white;\n\t\tvar texB = this.getInputData(2) || white;\n\t\tvar texA = this.getInputData(3) || white;\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tif (!LGraphChannelsTexture._shader) {\n\t\t\tLGraphChannelsTexture._shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphChannelsTexture.pixel_shader\n\t\t\t);\n\t\t}\n\t\tvar shader = LGraphChannelsTexture._shader;\n\n\t\tvar w = Math.max(texR.width, texG.width, texB.width, texA.width);\n\t\tvar h = Math.max(\n\t\t\ttexR.height,\n\t\t\ttexG.height,\n\t\t\ttexB.height,\n\t\t\ttexA.height\n\t\t);\n\t\tvar type =\n\t\t\tthis.properties.precision == LGraphTexture.HIGH\n\t\t\t\t? LGraphTexture.HIGH_PRECISION_FORMAT\n\t\t\t\t: gl.UNSIGNED_BYTE;\n\n\t\tif (\n\t\t\t!this._texture ||\n\t\t\tthis._texture.width != w ||\n\t\t\tthis._texture.height != h ||\n\t\t\tthis._texture.type != type\n\t\t) {\n\t\t\tthis._texture = new GL.Texture(w, h, {\n\t\t\t\ttype: type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tvar color = this._color;\n\t\tcolor[0] = this.properties.R;\n\t\tcolor[1] = this.properties.G;\n\t\tcolor[2] = this.properties.B;\n\t\tcolor[3] = this.properties.A;\n\t\tvar uniforms = this._uniforms;\n\n\t\tthis._texture.drawTo(function() {\n\t\t\ttexR.bind(0);\n\t\t\ttexG.bind(1);\n\t\t\ttexB.bind(2);\n\t\t\ttexA.bind(3);\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\t\tthis.setOutputData(0, this._texture);\n\t};\n\n\tLGraphChannelsTexture.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_textureR;\\n\\\n\t\tuniform sampler2D u_textureG;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\tuniform sampler2D u_textureA;\\n\\\n\t\tuniform vec4 u_color;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t   gl_FragColor = u_color * vec4( \\\n\t\t\t\t\ttexture2D(u_textureR, v_coord).r,\\\n\t\t\t\t\ttexture2D(u_textureG, v_coord).r,\\\n\t\t\t\t\ttexture2D(u_textureB, v_coord).r,\\\n\t\t\t\t\ttexture2D(u_textureA, v_coord).r);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/channelsTexture\",\n\t\tLGraphChannelsTexture\n\t);\n\n\t// Texture Color *****************************************\n\tfunction LGraphTextureColor() {\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\n\t\tthis._tex_color = vec4.create();\n\t\tthis.properties = {\n\t\t\tcolor: vec4.create(),\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureColor.title = \"Color\";\n\tLGraphTextureColor.desc =\n\t\t\"Generates a 1x1 texture with a constant color\";\n\n\tLGraphTextureColor.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureColor.prototype.onDrawBackground = function(ctx) {\n\t\tvar c = this.properties.color;\n\t\tctx.fillStyle =\n\t\t\t\"rgb(\" +\n\t\t\tMath.floor(clamp(c[0], 0, 1) * 255) +\n\t\t\t\",\" +\n\t\t\tMath.floor(clamp(c[1], 0, 1) * 255) +\n\t\t\t\",\" +\n\t\t\tMath.floor(clamp(c[2], 0, 1) * 255) +\n\t\t\t\")\";\n\t\tif (this.flags.collapsed) {\n\t\t\tthis.boxcolor = ctx.fillStyle;\n\t\t} else {\n\t\t\tctx.fillRect(0, 0, this.size[0], this.size[1]);\n\t\t}\n\t};\n\n\tLGraphTextureColor.prototype.onExecute = function() {\n\t\tvar type =\n\t\t\tthis.properties.precision == LGraphTexture.HIGH\n\t\t\t\t? LGraphTexture.HIGH_PRECISION_FORMAT\n\t\t\t\t: gl.UNSIGNED_BYTE;\n\n\t\tif (!this._tex || this._tex.type != type) {\n\t\t\tthis._tex = new GL.Texture(1, 1, {\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\ttype: type,\n\t\t\t\tminFilter: gl.NEAREST\n\t\t\t});\n\t\t}\n\t\tvar color = this.properties.color;\n\n\t\tif (this.inputs) {\n\t\t\tfor (var i = 0; i < this.inputs.length; i++) {\n\t\t\t\tvar input = this.inputs[i];\n\t\t\t\tvar v = this.getInputData(i);\n\t\t\t\tif (v === undefined) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tswitch (input.name) {\n\t\t\t\t\tcase \"RGB\":\n\t\t\t\t\tcase \"RGBA\":\n\t\t\t\t\t\tcolor.set(v);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"R\":\n\t\t\t\t\t\tcolor[0] = v;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"G\":\n\t\t\t\t\t\tcolor[1] = v;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"B\":\n\t\t\t\t\t\tcolor[2] = v;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"A\":\n\t\t\t\t\t\tcolor[3] = v;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (vec4.sqrDist(this._tex_color, color) > 0.001) {\n\t\t\tthis._tex_color.set(color);\n\t\t\tthis._tex.fill(color);\n\t\t}\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureColor.prototype.onGetInputs = function() {\n\t\treturn [\n\t\t\t[\"RGB\", \"vec3\"],\n\t\t\t[\"RGBA\", \"vec4\"],\n\t\t\t[\"R\", \"number\"],\n\t\t\t[\"G\", \"number\"],\n\t\t\t[\"B\", \"number\"],\n\t\t\t[\"A\", \"number\"]\n\t\t];\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/color\", LGraphTextureColor);\n\n\t// Texture Channels to Texture *****************************************\n\tfunction LGraphTextureGradient() {\n\t\tthis.addInput(\"A\", \"color\");\n\t\tthis.addInput(\"B\", \"color\");\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\n\t\tthis.properties = {\n\t\t\tangle: 0,\n\t\t\tscale: 1,\n\t\t\tA: [0, 0, 0],\n\t\t\tB: [1, 1, 1],\n\t\t\ttexture_size: 32\n\t\t};\n\t\tif (!LGraphTextureGradient._shader) {\n\t\t\tLGraphTextureGradient._shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureGradient.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tthis._uniforms = {\n\t\t\tu_angle: 0,\n\t\t\tu_colorA: vec3.create(),\n\t\t\tu_colorB: vec3.create()\n\t\t};\n\t}\n\n\tLGraphTextureGradient.title = \"Gradient\";\n\tLGraphTextureGradient.desc = \"Generates a gradient\";\n\tLGraphTextureGradient[\"@A\"] = { type: \"color\" };\n\tLGraphTextureGradient[\"@B\"] = { type: \"color\" };\n\tLGraphTextureGradient[\"@texture_size\"] = {\n\t\ttype: \"enum\",\n\t\tvalues: [32, 64, 128, 256, 512]\n\t};\n\n\tLGraphTextureGradient.prototype.onExecute = function() {\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tvar mesh = GL.Mesh.getScreenQuad();\n\t\tvar shader = LGraphTextureGradient._shader;\n\n\t\tvar A = this.getInputData(0);\n\t\tif (!A) {\n\t\t\tA = this.properties.A;\n\t\t}\n\t\tvar B = this.getInputData(1);\n\t\tif (!B) {\n\t\t\tB = this.properties.B;\n\t\t}\n\n\t\t//angle and scale\n\t\tfor (var i = 2; i < this.inputs.length; i++) {\n\t\t\tvar input = this.inputs[i];\n\t\t\tvar v = this.getInputData(i);\n\t\t\tif (v === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.properties[input.name] = v;\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\t\tthis._uniforms.u_angle = this.properties.angle * DEG2RAD;\n\t\tthis._uniforms.u_scale = this.properties.scale;\n\t\tvec3.copy(uniforms.u_colorA, A);\n\t\tvec3.copy(uniforms.u_colorB, B);\n\n\t\tvar size = parseInt(this.properties.texture_size);\n\t\tif (!this._tex || this._tex.width != size) {\n\t\t\tthis._tex = new GL.Texture(size, size, {\n\t\t\t\tformat: gl.RGB,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tthis._tex.drawTo(function() {\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureGradient.prototype.onGetInputs = function() {\n\t\treturn [[\"angle\", \"number\"], [\"scale\", \"number\"]];\n\t};\n\n\tLGraphTextureGradient.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform float u_angle;\\n\\\n\t\tuniform float u_scale;\\n\\\n\t\tuniform vec3 u_colorA;\\n\\\n\t\tuniform vec3 u_colorB;\\n\\\n\t\t\\n\\\n\t\tvec2 rotate(vec2 v, float angle)\\n\\\n\t\t{\\n\\\n\t\t\tvec2 result;\\n\\\n\t\t\tfloat _cos = cos(angle);\\n\\\n\t\t\tfloat _sin = sin(angle);\\n\\\n\t\t\tresult.x = v.x * _cos - v.y * _sin;\\n\\\n\t\t\tresult.y = v.x * _sin + v.y * _cos;\\n\\\n\t\t\treturn result;\\n\\\n\t\t}\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tfloat f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\\n\\\n\t\t\tvec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\\n\\\n\t\t   gl_FragColor = vec4(color,1.0);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/gradient\", LGraphTextureGradient);\n\n\t// Texture Mix *****************************************\n\tfunction LGraphTextureMix() {\n\t\tthis.addInput(\"A\", \"Texture\");\n\t\tthis.addInput(\"B\", \"Texture\");\n\t\tthis.addInput(\"Mixer\", \"Texture\");\n\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\t\tthis.properties = { factor: 0.5, size_from_biggest: true, invert: false, precision: LGraphTexture.DEFAULT };\n\t\tthis._uniforms = {\n\t\t\tu_textureA: 0,\n\t\t\tu_textureB: 1,\n\t\t\tu_textureMix: 2,\n\t\t\tu_mix: vec4.create()\n\t\t};\n\t}\n\n\tLGraphTextureMix.title = \"Mix\";\n\tLGraphTextureMix.desc = \"Generates a texture mixing two textures\";\n\n\tLGraphTextureMix.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureMix.prototype.onExecute = function() {\n\t\tvar texA = this.getInputData(0);\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, texA);\n\t\t\treturn;\n\t\t}\n\n\t\tvar texB = this.getInputData(1);\n\t\tif (!texA || !texB) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar texMix = this.getInputData(2);\n\n\t\tvar factor = this.getInputData(3);\n\n\t\tthis._tex = LGraphTexture.getTargetTexture(\n\t\t\tthis.properties.size_from_biggest && texB.width > texA.width ? texB : texA,\n\t\t\tthis._tex,\n\t\t\tthis.properties.precision\n\t\t);\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tvar shader = null;\n\t\tvar uniforms = this._uniforms;\n\t\tif (texMix) {\n\t\t\tshader = LGraphTextureMix._shader_tex;\n\t\t\tif (!shader) {\n\t\t\t\tshader = LGraphTextureMix._shader_tex = new GL.Shader(\n\t\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\tLGraphTextureMix.pixel_shader,\n\t\t\t\t\t{ MIX_TEX: \"\" }\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tshader = LGraphTextureMix._shader_factor;\n\t\t\tif (!shader) {\n\t\t\t\tshader = LGraphTextureMix._shader_factor = new GL.Shader(\n\t\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\tLGraphTextureMix.pixel_shader\n\t\t\t\t);\n\t\t\t}\n\t\t\tvar f = factor == null ? this.properties.factor : factor;\n\t\t\tuniforms.u_mix.set([f, f, f, f]);\n\t\t}\n\n\t\tvar invert = this.properties.invert;\n\n\t\tthis._tex.drawTo(function() {\n\t\t\ttexA.bind( invert ? 1 : 0 );\n\t\t\ttexB.bind( invert ? 0 : 1 );\n\t\t\tif (texMix) {\n\t\t\t\ttexMix.bind(2);\n\t\t\t}\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureMix.prototype.onGetInputs = function() {\n\t\treturn [[\"factor\", \"number\"]];\n\t};\n\n\tLGraphTextureMix.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_textureA;\\n\\\n\t\tuniform sampler2D u_textureB;\\n\\\n\t\t#ifdef MIX_TEX\\n\\\n\t\t\tuniform sampler2D u_textureMix;\\n\\\n\t\t#else\\n\\\n\t\t\tuniform vec4 u_mix;\\n\\\n\t\t#endif\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\t#ifdef MIX_TEX\\n\\\n\t\t\t   vec4 f = texture2D(u_textureMix, v_coord);\\n\\\n\t\t\t#else\\n\\\n\t\t\t   vec4 f = u_mix;\\n\\\n\t\t\t#endif\\n\\\n\t\t   gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), f );\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/mix\", LGraphTextureMix);\n\n\t// Texture Edges detection *****************************************\n\tfunction LGraphTextureEdges() {\n\t\tthis.addInput(\"Tex.\", \"Texture\");\n\n\t\tthis.addOutput(\"Edges\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tinvert: true,\n\t\t\tthreshold: false,\n\t\t\tfactor: 1,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\n\t\tif (!LGraphTextureEdges._shader) {\n\t\t\tLGraphTextureEdges._shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureEdges.pixel_shader\n\t\t\t);\n\t\t}\n\t}\n\n\tLGraphTextureEdges.title = \"Edges\";\n\tLGraphTextureEdges.desc = \"Detects edges\";\n\n\tLGraphTextureEdges.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureEdges.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._tex = LGraphTexture.getTargetTexture(\n\t\t\ttex,\n\t\t\tthis._tex,\n\t\t\tthis.properties.precision\n\t\t);\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tvar shader = LGraphTextureEdges._shader;\n\t\tvar invert = this.properties.invert;\n\t\tvar factor = this.properties.factor;\n\t\tvar threshold = this.properties.threshold ? 1 : 0;\n\n\t\tthis._tex.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader\n\t\t\t\t.uniforms({\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tu_isize: [1 / tex.width, 1 / tex.height],\n\t\t\t\t\tu_factor: factor,\n\t\t\t\t\tu_threshold: threshold,\n\t\t\t\t\tu_invert: invert ? 1 : 0\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureEdges.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 u_isize;\\n\\\n\t\tuniform int u_invert;\\n\\\n\t\tuniform float u_factor;\\n\\\n\t\tuniform float u_threshold;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 center = texture2D(u_texture, v_coord);\\n\\\n\t\t\tvec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\\n\\\n\t\t\tvec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\\n\\\n\t\t\tvec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\\n\\\n\t\t\tvec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\\n\\\n\t\t\tvec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\\n\\\n\t\t\tdiff *= u_factor;\\n\\\n\t\t\tif(u_invert == 1)\\n\\\n\t\t\t\tdiff.xyz = vec3(1.0) - diff.xyz;\\n\\\n\t\t\tif( u_threshold == 0.0 )\\n\\\n\t\t\t\tgl_FragColor = vec4( diff.xyz, center.a );\\n\\\n\t\t\telse\\n\\\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\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType(\"texture/edges\", LGraphTextureEdges);\n\n\t// Texture Depth *****************************************\n\tfunction LGraphTextureDepthRange() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addInput(\"Distance\", \"number\");\n\t\tthis.addInput(\"Range\", \"number\");\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tdistance: 100,\n\t\t\trange: 50,\n\t\t\tonly_depth: false,\n\t\t\thigh_precision: false\n\t\t};\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_distance: 100,\n\t\t\tu_range: 50,\n\t\t\tu_camera_planes: null\n\t\t};\n\t}\n\n\tLGraphTextureDepthRange.title = \"Depth Range\";\n\tLGraphTextureDepthRange.desc = \"Generates a texture with a depth range\";\n\n\tLGraphTextureDepthRange.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar precision = gl.UNSIGNED_BYTE;\n\t\tif (this.properties.high_precision) {\n\t\t\tprecision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT;\n\t\t}\n\n\t\tif (\n\t\t\t!this._temp_texture ||\n\t\t\tthis._temp_texture.type != precision ||\n\t\t\tthis._temp_texture.width != tex.width ||\n\t\t\tthis._temp_texture.height != tex.height\n\t\t) {\n\t\t\tthis._temp_texture = new GL.Texture(tex.width, tex.height, {\n\t\t\t\ttype: precision,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\n\t\t//iterations\n\t\tvar distance = this.properties.distance;\n\t\tif (this.isInputConnected(1)) {\n\t\t\tdistance = this.getInputData(1);\n\t\t\tthis.properties.distance = distance;\n\t\t}\n\n\t\tvar range = this.properties.range;\n\t\tif (this.isInputConnected(2)) {\n\t\t\trange = this.getInputData(2);\n\t\t\tthis.properties.range = range;\n\t\t}\n\n\t\tuniforms.u_distance = distance;\n\t\tuniforms.u_range = range;\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tif (!LGraphTextureDepthRange._shader) {\n\t\t\tLGraphTextureDepthRange._shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureDepthRange.pixel_shader\n\t\t\t);\n\t\t\tLGraphTextureDepthRange._shader_onlydepth = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureDepthRange.pixel_shader,\n\t\t\t\t{ ONLY_DEPTH: \"\" }\n\t\t\t);\n\t\t}\n\t\tvar shader = this.properties.only_depth\n\t\t\t? LGraphTextureDepthRange._shader_onlydepth\n\t\t\t: LGraphTextureDepthRange._shader;\n\n\t\t//NEAR AND FAR PLANES\n\t\tvar planes = null;\n\t\tif (tex.near_far_planes) {\n\t\t\tplanes = tex.near_far_planes;\n\t\t} else if (window.LS && LS.Renderer._main_camera) {\n\t\t\tplanes = LS.Renderer._main_camera._uniforms.u_camera_planes;\n\t\t} else {\n\t\t\tplanes = [0.1, 1000];\n\t\t} //hardcoded\n\t\tuniforms.u_camera_planes = planes;\n\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\n\t\tthis._temp_texture.near_far_planes = planes;\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\tLGraphTextureDepthRange.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 u_camera_planes;\\n\\\n\t\tuniform float u_distance;\\n\\\n\t\tuniform float u_range;\\n\\\n\t\t\\n\\\n\t\tfloat LinearDepth()\\n\\\n\t\t{\\n\\\n\t\t\tfloat zNear = u_camera_planes.x;\\n\\\n\t\t\tfloat zFar = u_camera_planes.y;\\n\\\n\t\t\tfloat depth = texture2D(u_texture, v_coord).x;\\n\\\n\t\t\tdepth = depth * 2.0 - 1.0;\\n\\\n\t\t\treturn zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tfloat depth = LinearDepth();\\n\\\n\t\t\t#ifdef ONLY_DEPTH\\n\\\n\t\t\t   gl_FragColor = vec4(depth);\\n\\\n\t\t\t#else\\n\\\n\t\t\t\tfloat diff = abs(depth * u_camera_planes.y - u_distance);\\n\\\n\t\t\t\tfloat dof = 1.0;\\n\\\n\t\t\t\tif(diff <= u_range)\\n\\\n\t\t\t\t\tdof = diff / u_range;\\n\\\n\t\t\t   gl_FragColor = vec4(dof);\\n\\\n\t\t\t#endif\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType( \"texture/depth_range\", LGraphTextureDepthRange );\n\n\n\t// Texture Depth *****************************************\n\tfunction LGraphTextureLinearDepth() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"Texture\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tprecision: LGraphTexture.DEFAULT,\n\t\t\tinvert: false\n\t\t};\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_camera_planes: null, //filled later\n\t\t\tu_ires: vec2.create()\n\t\t};\n\t}\n\n\tLGraphTextureLinearDepth.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureLinearDepth.title = \"Linear Depth\";\n\tLGraphTextureLinearDepth.desc = \"Creates a color texture with linear depth\";\n\n\tLGraphTextureLinearDepth.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex || (tex.format != gl.DEPTH_COMPONENT && tex.format != gl.DEPTH_STENCIL) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar precision = this.properties.precision == LGraphTexture.HIGH ? gl.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE;\n\n\t\tif ( !this._temp_texture || this._temp_texture.type != precision || this._temp_texture.width != tex.width || this._temp_texture.height != tex.height ) {\n\t\t\tthis._temp_texture = new GL.Texture(tex.width, tex.height, {\n\t\t\t\ttype: precision,\n\t\t\t\tformat: gl.RGB,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_invert = this.properties.invert ? 1 : 0;\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tif(!LGraphTextureLinearDepth._shader)\n\t\t\tLGraphTextureLinearDepth._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearDepth.pixel_shader);\n\t\tvar shader = LGraphTextureLinearDepth._shader;\n\n\t\t//NEAR AND FAR PLANES\n\t\tvar planes = null;\n\t\tif (tex.near_far_planes) {\n\t\t\tplanes = tex.near_far_planes;\n\t\t} else if (window.LS && LS.Renderer._main_camera) {\n\t\t\tplanes = LS.Renderer._main_camera._uniforms.u_camera_planes;\n\t\t} else {\n\t\t\tplanes = [0.1, 1000];\n\t\t} //hardcoded\n\t\tuniforms.u_camera_planes = planes;\n\t\t//uniforms.u_ires.set([1/tex.width, 1/tex.height]);\n\t\tuniforms.u_ires.set([0,0]);\n\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\n\t\tthis._temp_texture.near_far_planes = planes;\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\tLGraphTextureLinearDepth.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 u_camera_planes;\\n\\\n\t\tuniform int u_invert;\\n\\\n\t\tuniform vec2 u_ires;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tfloat zNear = u_camera_planes.x;\\n\\\n\t\t\tfloat zFar = u_camera_planes.y;\\n\\\n\t\t\tfloat depth = texture2D(u_texture, v_coord + u_ires*0.5).x * 2.0 - 1.0;\\n\\\n\t\t\tfloat f = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\\n\\\n\t\t\tif( u_invert == 1 )\\n\\\n\t\t\t\tf = 1.0 - f;\\n\\\n\t\t\tgl_FragColor = vec4(vec3(f),1.0);\\n\\\n\t\t}\\n\\\n\t\t\";\n\n\tLiteGraph.registerNodeType( \"texture/linear_depth\", LGraphTextureLinearDepth );\n\n\t// Texture Blur *****************************************\n\tfunction LGraphTextureBlur() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addInput(\"Iterations\", \"number\");\n\t\tthis.addInput(\"Intensity\", \"number\");\n\t\tthis.addOutput(\"Blurred\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tintensity: 1,\n\t\t\titerations: 1,\n\t\t\tpreserve_aspect: false,\n\t\t\tscale: [1, 1],\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureBlur.title = \"Blur\";\n\tLGraphTextureBlur.desc = \"Blur a texture\";\n\n\tLGraphTextureBlur.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureBlur.max_iterations = 20;\n\n\tLGraphTextureBlur.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar temp = this._final_texture;\n\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\t//we need two textures to do the blurring\n\t\t\t//this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });\n\t\t\ttemp = this._final_texture = new GL.Texture(\n\t\t\t\ttex.width,\n\t\t\t\ttex.height,\n\t\t\t\t{ type: tex.type, format: gl.RGBA, filter: gl.LINEAR }\n\t\t\t);\n\t\t}\n\n\t\t//iterations\n\t\tvar iterations = this.properties.iterations;\n\t\tif (this.isInputConnected(1)) {\n\t\t\titerations = this.getInputData(1);\n\t\t\tthis.properties.iterations = iterations;\n\t\t}\n\t\titerations = Math.min(\n\t\t\tMath.floor(iterations),\n\t\t\tLGraphTextureBlur.max_iterations\n\t\t);\n\t\tif (iterations == 0) {\n\t\t\t//skip blurring\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar intensity = this.properties.intensity;\n\t\tif (this.isInputConnected(2)) {\n\t\t\tintensity = this.getInputData(2);\n\t\t\tthis.properties.intensity = intensity;\n\t\t}\n\n\t\t//blur sometimes needs an aspect correction\n\t\tvar aspect = LiteGraph.camera_aspect;\n\t\tif (!aspect && window.gl !== undefined) {\n\t\t\taspect = gl.canvas.height / gl.canvas.width;\n\t\t}\n\t\tif (!aspect) {\n\t\t\taspect = 1;\n\t\t}\n\t\taspect = this.properties.preserve_aspect ? aspect : 1;\n\n\t\tvar scale = this.properties.scale || [1, 1];\n\t\ttex.applyBlur(aspect * scale[0], scale[1], intensity, temp);\n\t\tfor (var i = 1; i < iterations; ++i) {\n\t\t\ttemp.applyBlur(\n\t\t\t\taspect * scale[0] * (i + 1),\n\t\t\t\tscale[1] * (i + 1),\n\t\t\t\tintensity\n\t\t\t);\n\t\t}\n\n\t\tthis.setOutputData(0, temp);\n\t};\n\n\t/*\nLGraphTextureBlur.pixel_shader = \"precision highp float;\\n\\\n\t\tprecision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec2 u_offset;\\n\\\n\t\tuniform float u_intensity;\\n\\\n\t\tvoid main() {\\n\\\n\t\t   vec4 sum = vec4(0.0);\\n\\\n\t\t   vec4 center = texture2D(u_texture, v_coord);\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * -4.0) * 0.05/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * -3.0) * 0.09/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * -2.0) * 0.12/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * -1.0) * 0.15/0.98;\\n\\\n\t\t   sum += center * 0.16/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * 4.0) * 0.05/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * 3.0) * 0.09/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * 2.0) * 0.12/0.98;\\n\\\n\t\t   sum += texture2D(u_texture, v_coord + u_offset * 1.0) * 0.15/0.98;\\n\\\n\t\t   gl_FragColor = u_intensity * sum;\\n\\\n\t\t}\\n\\\n\t\t\";\n*/\n\n\tLiteGraph.registerNodeType(\"texture/blur\", LGraphTextureBlur);\n\n\t//Independent glow FX\n\t//based on https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/\n\tfunction FXGlow()\n\t{\n\t\tthis.intensity = 0.5;\n\t\tthis.persistence = 0.6;\n\t\tthis.iterations = 8;\n\t\tthis.threshold = 0.8;\n\t\tthis.scale = 1;\n\n\t\tthis.dirt_texture = null;\n\t\tthis.dirt_factor = 0.5;\n\n\t\tthis._textures = [];\n\t\tthis._uniforms = {\n\t\t\tu_intensity: 1,\n\t\t\tu_texture: 0,\n\t\t\tu_glow_texture: 1,\n\t\t\tu_threshold: 0,\n\t\t\tu_texel_size: vec2.create()\n\t\t};\n\t}\n\n\tFXGlow.prototype.applyFX = function( tex, output_texture, glow_texture, average_texture ) {\n\n\t\tvar width = tex.width;\n\t\tvar height = tex.height;\n\n\t\tvar texture_info = {\n\t\t\tformat: tex.format,\n\t\t\ttype: tex.type,\n\t\t\tminFilter: GL.LINEAR,\n\t\t\tmagFilter: GL.LINEAR,\n\t\t\twrap: gl.CLAMP_TO_EDGE\n\t\t};\n\n\t\tvar uniforms = this._uniforms;\n\t\tvar textures = this._textures;\n\n\t\t//cut\n\t\tvar shader = FXGlow._cut_shader;\n\t\tif (!shader) {\n\t\t\tshader = FXGlow._cut_shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tFXGlow.cut_pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\tgl.disable(gl.BLEND);\n\n\t\tuniforms.u_threshold = this.threshold;\n\t\tvar currentDestination = (textures[0] = GL.Texture.getTemporary(\n\t\t\twidth,\n\t\t\theight,\n\t\t\ttexture_info\n\t\t));\n\t\ttex.blit( currentDestination, shader.uniforms(uniforms) );\n\t\tvar currentSource = currentDestination;\n\n\t\tvar iterations = this.iterations;\n\t\titerations = clamp(iterations, 1, 16) | 0;\n\t\tvar texel_size = uniforms.u_texel_size;\n\t\tvar intensity = this.intensity;\n\n\t\tuniforms.u_intensity = 1;\n\t\tuniforms.u_delta = this.scale; //1\n\n\t\t//downscale/upscale shader\n\t\tvar shader = FXGlow._shader;\n\t\tif (!shader) {\n\t\t\tshader = FXGlow._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tFXGlow.scale_pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar i = 1;\n\t\t//downscale\n\t\tfor (; i < iterations; i++) {\n\t\t\twidth = width >> 1;\n\t\t\tif ((height | 0) > 1) {\n\t\t\t\theight = height >> 1;\n\t\t\t}\n\t\t\tif (width < 2) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcurrentDestination = textures[i] = GL.Texture.getTemporary(\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\ttexture_info\n\t\t\t);\n\t\t\ttexel_size[0] = 1 / currentSource.width;\n\t\t\ttexel_size[1] = 1 / currentSource.height;\n\t\t\tcurrentSource.blit(\n\t\t\t\tcurrentDestination,\n\t\t\t\tshader.uniforms(uniforms)\n\t\t\t);\n\t\t\tcurrentSource = currentDestination;\n\t\t}\n\n\t\t//average\n\t\tif (average_texture) {\n\t\t\ttexel_size[0] = 1 / currentSource.width;\n\t\t\ttexel_size[1] = 1 / currentSource.height;\n\t\t\tuniforms.u_intensity = intensity;\n\t\t\tuniforms.u_delta = 1;\n\t\t\tcurrentSource.blit(average_texture, shader.uniforms(uniforms));\n\t\t}\n\n\t\t//upscale and blend\n\t\tgl.enable(gl.BLEND);\n\t\tgl.blendFunc(gl.ONE, gl.ONE);\n\t\tuniforms.u_intensity = this.persistence;\n\t\tuniforms.u_delta = 0.5;\n\n\t\t// i-=2 => -1 to point to last element in array, -1 to go to texture above\n\t\tfor ( i -= 2; i >= 0; i-- ) \n\t\t{\n\t\t\tcurrentDestination = textures[i];\n\t\t\ttextures[i] = null;\n\t\t\ttexel_size[0] = 1 / currentSource.width;\n\t\t\ttexel_size[1] = 1 / currentSource.height;\n\t\t\tcurrentSource.blit(\n\t\t\t\tcurrentDestination,\n\t\t\t\tshader.uniforms(uniforms)\n\t\t\t);\n\t\t\tGL.Texture.releaseTemporary(currentSource);\n\t\t\tcurrentSource = currentDestination;\n\t\t}\n\t\tgl.disable(gl.BLEND);\n\n\t\t//glow\n\t\tif (glow_texture) {\n\t\t\tcurrentSource.blit(glow_texture);\n\t\t}\n\n\t\t//final composition\n\t\tif ( output_texture ) {\n\t\t\tvar final_texture = output_texture;\n\t\t\tvar dirt_texture = this.dirt_texture;\n\t\t\tvar dirt_factor = this.dirt_factor;\n\t\t\tuniforms.u_intensity = intensity;\n\n\t\t\tshader = dirt_texture\n\t\t\t\t? FXGlow._dirt_final_shader\n\t\t\t\t: FXGlow._final_shader;\n\t\t\tif (!shader) {\n\t\t\t\tif (dirt_texture) {\n\t\t\t\t\tshader = FXGlow._dirt_final_shader = new GL.Shader(\n\t\t\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\t\tFXGlow.final_pixel_shader,\n\t\t\t\t\t\t{ USE_DIRT: \"\" }\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tshader = FXGlow._final_shader = new GL.Shader(\n\t\t\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\t\tFXGlow.final_pixel_shader\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfinal_texture.drawTo(function() {\n\t\t\t\ttex.bind(0);\n\t\t\t\tcurrentSource.bind(1);\n\t\t\t\tif (dirt_texture) {\n\t\t\t\t\tshader.setUniform(\"u_dirt_factor\", dirt_factor);\n\t\t\t\t\tshader.setUniform(\n\t\t\t\t\t\t\"u_dirt_texture\",\n\t\t\t\t\t\tdirt_texture.bind(2)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tshader.toViewport(uniforms);\n\t\t\t});\n\t\t}\n\n\t\tGL.Texture.releaseTemporary(currentSource);\n\t};\n\n\tFXGlow.cut_pixel_shader =\n\t\t\"precision highp float;\\n\\\n\tvarying vec2 v_coord;\\n\\\n\tuniform sampler2D u_texture;\\n\\\n\tuniform float u_threshold;\\n\\\n\tvoid main() {\\n\\\n\t\tgl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\\n\\\n\t}\";\n\n\tFXGlow.scale_pixel_shader =\n\t\t\"precision highp float;\\n\\\n\tvarying vec2 v_coord;\\n\\\n\tuniform sampler2D u_texture;\\n\\\n\tuniform vec2 u_texel_size;\\n\\\n\tuniform float u_delta;\\n\\\n\tuniform float u_intensity;\\n\\\n\t\\n\\\n\tvec4 sampleBox(vec2 uv) {\\n\\\n\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\\n\\\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\\\n\t\treturn s * 0.25;\\n\\\n\t}\\n\\\n\tvoid main() {\\n\\\n\t\tgl_FragColor = u_intensity * sampleBox( v_coord );\\n\\\n\t}\";\n\n\tFXGlow.final_pixel_shader =\n\t\t\"precision highp float;\\n\\\n\tvarying vec2 v_coord;\\n\\\n\tuniform sampler2D u_texture;\\n\\\n\tuniform sampler2D u_glow_texture;\\n\\\n\t#ifdef USE_DIRT\\n\\\n\t\tuniform sampler2D u_dirt_texture;\\n\\\n\t#endif\\n\\\n\tuniform vec2 u_texel_size;\\n\\\n\tuniform float u_delta;\\n\\\n\tuniform float u_intensity;\\n\\\n\tuniform float u_dirt_factor;\\n\\\n\t\\n\\\n\tvec4 sampleBox(vec2 uv) {\\n\\\n\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\\n\\\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\\\n\t\treturn s * 0.25;\\n\\\n\t}\\n\\\n\tvoid main() {\\n\\\n\t\tvec4 glow = sampleBox( v_coord );\\n\\\n\t\t#ifdef USE_DIRT\\n\\\n\t\t\tglow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\\n\\\n\t\t#endif\\n\\\n\t\tgl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\\n\\\n\t}\";\n\n\n\t// Texture Glow *****************************************\n\tfunction LGraphTextureGlow() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"dirt\", \"Texture\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.addOutput(\"glow\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tenabled: true,\n\t\t\tintensity: 1,\n\t\t\tpersistence: 0.99,\n\t\t\titerations: 16,\n\t\t\tthreshold: 0,\n\t\t\tscale: 1,\n\t\t\tdirt_factor: 0.5,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\n\t\tthis.fx = new FXGlow();\n\t}\n\n\tLGraphTextureGlow.title = \"Glow\";\n\tLGraphTextureGlow.desc = \"Filters a texture giving it a glow effect\";\n\n\tLGraphTextureGlow.widgets_info = {\n\t\titerations: {\n\t\t\ttype: \"number\",\n\t\t\tmin: 0,\n\t\t\tmax: 16,\n\t\t\tstep: 1,\n\t\t\tprecision: 0\n\t\t},\n\t\tthreshold: {\n\t\t\ttype: \"number\",\n\t\t\tmin: 0,\n\t\t\tmax: 10,\n\t\t\tstep: 0.01,\n\t\t\tprecision: 2\n\t\t},\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureGlow.prototype.onGetInputs = function() {\n\t\treturn [\n\t\t\t[\"enabled\", \"boolean\"],\n\t\t\t[\"threshold\", \"number\"],\n\t\t\t[\"intensity\", \"number\"],\n\t\t\t[\"persistence\", \"number\"],\n\t\t\t[\"iterations\", \"number\"],\n\t\t\t[\"dirt_factor\", \"number\"]\n\t\t];\n\t};\n\n\tLGraphTextureGlow.prototype.onGetOutputs = function() {\n\t\treturn [[\"average\", \"Texture\"]];\n\t};\n\n\tLGraphTextureGlow.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isAnyOutputConnected()) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (\n\t\t\tthis.properties.precision === LGraphTexture.PASS_THROUGH ||\n\t\t\tthis.getInputOrProperty(\"enabled\") === false\n\t\t) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = tex.width;\n\t\tvar height = tex.height;\n\n\t\tvar fx = this.fx;\n\t\tfx.threshold = this.getInputOrProperty(\"threshold\");\n\t\tfx.iterations = this.getInputOrProperty(\"iterations\");\n\t\tfx.intensity = this.getInputOrProperty(\"intensity\");\n\t\tfx.persistence = this.getInputOrProperty(\"persistence\");\n\t\tfx.dirt_texture = this.getInputData(1);\n\t\tfx.dirt_factor = this.getInputOrProperty(\"dirt_factor\");\n\t\tfx.scale = this.properties.scale;\n\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision, tex );\n\n\t\tvar average_texture = null;\n\t\tif (this.isOutputConnected(2)) {\n\t\t\taverage_texture = this._average_texture;\n\t\t\tif (\n\t\t\t\t!average_texture ||\n\t\t\t\taverage_texture.type != tex.type ||\n\t\t\t\taverage_texture.format != tex.format\n\t\t\t) {\n\t\t\t\taverage_texture = this._average_texture = new GL.Texture(\n\t\t\t\t\t1,\n\t\t\t\t\t1,\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: tex.type,\n\t\t\t\t\t\tformat: tex.format,\n\t\t\t\t\t\tfilter: gl.LINEAR\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tvar glow_texture = null;\n\t\tif (this.isOutputConnected(1)) {\n\t\t\tglow_texture = this._glow_texture;\n\t\t\tif (\n\t\t\t\t!glow_texture ||\n\t\t\t\tglow_texture.width != tex.width ||\n\t\t\t\tglow_texture.height != tex.height ||\n\t\t\t\tglow_texture.type != type ||\n\t\t\t\tglow_texture.format != tex.format\n\t\t\t) {\n\t\t\t\tglow_texture = this._glow_texture = new GL.Texture(\n\t\t\t\t\ttex.width,\n\t\t\t\t\ttex.height,\n\t\t\t\t\t{ type: type, format: tex.format, filter: gl.LINEAR }\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tvar final_texture = null;\n\t\tif (this.isOutputConnected(0)) {\n\t\t\tfinal_texture = this._final_texture;\n\t\t\tif (\n\t\t\t\t!final_texture ||\n\t\t\t\tfinal_texture.width != tex.width ||\n\t\t\t\tfinal_texture.height != tex.height ||\n\t\t\t\tfinal_texture.type != type ||\n\t\t\t\tfinal_texture.format != tex.format\n\t\t\t) {\n\t\t\t\tfinal_texture = this._final_texture = new GL.Texture(\n\t\t\t\t\ttex.width,\n\t\t\t\t\ttex.height,\n\t\t\t\t\t{ type: type, format: tex.format, filter: gl.LINEAR }\n\t\t\t\t);\n\t\t\t}\n\n\t\t}\n\n\t\t//apply FX\n\t\tfx.applyFX(tex, final_texture, glow_texture, average_texture );\n\n\t\tif (this.isOutputConnected(0))\n\t\t\tthis.setOutputData(0, final_texture);\n\n\t\tif (this.isOutputConnected(1))\n\t\t\tthis.setOutputData(1, average_texture);\n\n\t\tif (this.isOutputConnected(2))\n\t\t\tthis.setOutputData(2, glow_texture);\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/glow\", LGraphTextureGlow);\n\n\t// Texture Filter *****************************************\n\tfunction LGraphTextureKuwaharaFilter() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"Filtered\", \"Texture\");\n\t\tthis.properties = { intensity: 1, radius: 5 };\n\t}\n\n\tLGraphTextureKuwaharaFilter.title = \"Kuwahara Filter\";\n\tLGraphTextureKuwaharaFilter.desc =\n\t\t\"Filters a texture giving an artistic oil canvas painting\";\n\n\tLGraphTextureKuwaharaFilter.max_radius = 10;\n\tLGraphTextureKuwaharaFilter._shaders = [];\n\n\tLGraphTextureKuwaharaFilter.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar temp = this._temp_texture;\n\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\tthis._temp_texture = new GL.Texture(tex.width, tex.height, {\n\t\t\t\ttype: tex.type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\t//iterations\n\t\tvar radius = this.properties.radius;\n\t\tradius = Math.min(\n\t\t\tMath.floor(radius),\n\t\t\tLGraphTextureKuwaharaFilter.max_radius\n\t\t);\n\t\tif (radius == 0) {\n\t\t\t//skip blurring\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar intensity = this.properties.intensity;\n\n\t\t//blur sometimes needs an aspect correction\n\t\tvar aspect = LiteGraph.camera_aspect;\n\t\tif (!aspect && window.gl !== undefined) {\n\t\t\taspect = gl.canvas.height / gl.canvas.width;\n\t\t}\n\t\tif (!aspect) {\n\t\t\taspect = 1;\n\t\t}\n\t\taspect = this.properties.preserve_aspect ? aspect : 1;\n\n\t\tif (!LGraphTextureKuwaharaFilter._shaders[radius]) {\n\t\t\tLGraphTextureKuwaharaFilter._shaders[radius] = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureKuwaharaFilter.pixel_shader,\n\t\t\t\t{ RADIUS: radius.toFixed(0) }\n\t\t\t);\n\t\t}\n\n\t\tvar shader = LGraphTextureKuwaharaFilter._shaders[radius];\n\t\tvar mesh = GL.Mesh.getScreenQuad();\n\t\ttex.bind(0);\n\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\tshader\n\t\t\t\t.uniforms({\n\t\t\t\t\tu_texture: 0,\n\t\t\t\t\tu_intensity: intensity,\n\t\t\t\t\tu_resolution: [tex.width, tex.height],\n\t\t\t\t\tu_iResolution: [1 / tex.width, 1 / tex.height]\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\t//from https://www.shadertoy.com/view/MsXSz4\n\tLGraphTextureKuwaharaFilter.pixel_shader =\n\t\t\"\\n\\\nprecision highp float;\\n\\\nvarying vec2 v_coord;\\n\\\nuniform sampler2D u_texture;\\n\\\nuniform float u_intensity;\\n\\\nuniform vec2 u_resolution;\\n\\\nuniform vec2 u_iResolution;\\n\\\n#ifndef RADIUS\\n\\\n\t#define RADIUS 7\\n\\\n#endif\\n\\\nvoid main() {\\n\\\n\\n\\\n\tconst int radius = RADIUS;\\n\\\n\tvec2 fragCoord = v_coord;\\n\\\n\tvec2 src_size = u_iResolution;\\n\\\n\tvec2 uv = v_coord;\\n\\\n\tfloat n = float((radius + 1) * (radius + 1));\\n\\\n\tint i;\\n\\\n\tint j;\\n\\\n\tvec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\\n\\\n\tvec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\\n\\\n\tvec3 c;\\n\\\n\t\\n\\\n\tfor (int j = -radius; j <= 0; ++j)  {\\n\\\n\t\tfor (int i = -radius; i <= 0; ++i)  {\\n\\\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\\n\\\n\t\t\tm0 += c;\\n\\\n\t\t\ts0 += c * c;\\n\\\n\t\t}\\n\\\n\t}\\n\\\n\t\\n\\\n\tfor (int j = -radius; j <= 0; ++j)  {\\n\\\n\t\tfor (int i = 0; i <= radius; ++i)  {\\n\\\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\\n\\\n\t\t\tm1 += c;\\n\\\n\t\t\ts1 += c * c;\\n\\\n\t\t}\\n\\\n\t}\\n\\\n\t\\n\\\n\tfor (int j = 0; j <= radius; ++j)  {\\n\\\n\t\tfor (int i = 0; i <= radius; ++i)  {\\n\\\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\\n\\\n\t\t\tm2 += c;\\n\\\n\t\t\ts2 += c * c;\\n\\\n\t\t}\\n\\\n\t}\\n\\\n\t\\n\\\n\tfor (int j = 0; j <= radius; ++j)  {\\n\\\n\t\tfor (int i = -radius; i <= 0; ++i)  {\\n\\\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\\n\\\n\t\t\tm3 += c;\\n\\\n\t\t\ts3 += c * c;\\n\\\n\t\t}\\n\\\n\t}\\n\\\n\t\\n\\\n\tfloat min_sigma2 = 1e+2;\\n\\\n\tm0 /= n;\\n\\\n\ts0 = abs(s0 / n - m0 * m0);\\n\\\n\t\\n\\\n\tfloat sigma2 = s0.r + s0.g + s0.b;\\n\\\n\tif (sigma2 < min_sigma2) {\\n\\\n\t\tmin_sigma2 = sigma2;\\n\\\n\t\tgl_FragColor = vec4(m0, 1.0);\\n\\\n\t}\\n\\\n\t\\n\\\n\tm1 /= n;\\n\\\n\ts1 = abs(s1 / n - m1 * m1);\\n\\\n\t\\n\\\n\tsigma2 = s1.r + s1.g + s1.b;\\n\\\n\tif (sigma2 < min_sigma2) {\\n\\\n\t\tmin_sigma2 = sigma2;\\n\\\n\t\tgl_FragColor = vec4(m1, 1.0);\\n\\\n\t}\\n\\\n\t\\n\\\n\tm2 /= n;\\n\\\n\ts2 = abs(s2 / n - m2 * m2);\\n\\\n\t\\n\\\n\tsigma2 = s2.r + s2.g + s2.b;\\n\\\n\tif (sigma2 < min_sigma2) {\\n\\\n\t\tmin_sigma2 = sigma2;\\n\\\n\t\tgl_FragColor = vec4(m2, 1.0);\\n\\\n\t}\\n\\\n\t\\n\\\n\tm3 /= n;\\n\\\n\ts3 = abs(s3 / n - m3 * m3);\\n\\\n\t\\n\\\n\tsigma2 = s3.r + s3.g + s3.b;\\n\\\n\tif (sigma2 < min_sigma2) {\\n\\\n\t\tmin_sigma2 = sigma2;\\n\\\n\t\tgl_FragColor = vec4(m3, 1.0);\\n\\\n\t}\\n\\\n}\\n\\\n\";\n\n\tLiteGraph.registerNodeType(\n\t\t\"texture/kuwahara\",\n\t\tLGraphTextureKuwaharaFilter\n\t);\n\n\t// Texture  *****************************************\n\tfunction LGraphTextureXDoGFilter() {\n\t\tthis.addInput(\"Texture\", \"Texture\");\n\t\tthis.addOutput(\"Filtered\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tsigma: 1.4,\n\t\t\tk: 1.6,\n\t\t\tp: 21.7,\n\t\t\tepsilon: 79,\n\t\t\tphi: 0.017\n\t\t};\n\t}\n\n\tLGraphTextureXDoGFilter.title = \"XDoG Filter\";\n\tLGraphTextureXDoGFilter.desc =\n\t\t\"Filters a texture giving an artistic ink style\";\n\n\tLGraphTextureXDoGFilter.max_radius = 10;\n\tLGraphTextureXDoGFilter._shaders = [];\n\n\tLGraphTextureXDoGFilter.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar temp = this._temp_texture;\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\tthis._temp_texture = new GL.Texture(tex.width, tex.height, {\n\t\t\t\ttype: tex.type,\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tif (!LGraphTextureXDoGFilter._xdog_shader) {\n\t\t\tLGraphTextureXDoGFilter._xdog_shader = new GL.Shader(\n\t\t\t\tShader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureXDoGFilter.xdog_pixel_shader\n\t\t\t);\n\t\t}\n\t\tvar shader = LGraphTextureXDoGFilter._xdog_shader;\n\t\tvar mesh = GL.Mesh.getScreenQuad();\n\n\t\tvar sigma = this.properties.sigma;\n\t\tvar k = this.properties.k;\n\t\tvar p = this.properties.p;\n\t\tvar epsilon = this.properties.epsilon;\n\t\tvar phi = this.properties.phi;\n\t\ttex.bind(0);\n\t\tthis._temp_texture.drawTo(function() {\n\t\t\tshader\n\t\t\t\t.uniforms({\n\t\t\t\t\tsrc: 0,\n\t\t\t\t\tsigma: sigma,\n\t\t\t\t\tk: k,\n\t\t\t\t\tp: p,\n\t\t\t\t\tepsilon: epsilon,\n\t\t\t\t\tphi: phi,\n\t\t\t\t\tcvsWidth: tex.width,\n\t\t\t\t\tcvsHeight: tex.height\n\t\t\t\t})\n\t\t\t\t.draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\t//from https://github.com/RaymondMcGuire/GPU-Based-Image-Processing-Tools/blob/master/lib_webgl/scripts/main.js\n\tLGraphTextureXDoGFilter.xdog_pixel_shader =\n\t\t\"\\n\\\nprecision highp float;\\n\\\nuniform sampler2D src;\\n\\n\\\nuniform float cvsHeight;\\n\\\nuniform float cvsWidth;\\n\\n\\\nuniform float sigma;\\n\\\nuniform float k;\\n\\\nuniform float p;\\n\\\nuniform float epsilon;\\n\\\nuniform float phi;\\n\\\nvarying vec2 v_coord;\\n\\n\\\nfloat cosh(float val)\\n\\\n{\\n\\\n\tfloat tmp = exp(val);\\n\\\n\tfloat cosH = (tmp + 1.0 / tmp) / 2.0;\\n\\\n\treturn cosH;\\n\\\n}\\n\\n\\\nfloat tanh(float val)\\n\\\n{\\n\\\n\tfloat tmp = exp(val);\\n\\\n\tfloat tanH = (tmp - 1.0 / tmp) / (tmp + 1.0 / tmp);\\n\\\n\treturn tanH;\\n\\\n}\\n\\n\\\nfloat sinh(float val)\\n\\\n{\\n\\\n\tfloat tmp = exp(val);\\n\\\n\tfloat sinH = (tmp - 1.0 / tmp) / 2.0;\\n\\\n\treturn sinH;\\n\\\n}\\n\\n\\\nvoid main(void){\\n\\\n\tvec3 destColor = vec3(0.0);\\n\\\n\tfloat tFrag = 1.0 / cvsHeight;\\n\\\n\tfloat sFrag = 1.0 / cvsWidth;\\n\\\n\tvec2 Frag = vec2(sFrag,tFrag);\\n\\\n\tvec2 uv = gl_FragCoord.st;\\n\\\n\tfloat twoSigmaESquared = 2.0 * sigma * sigma;\\n\\\n\tfloat twoSigmaRSquared = twoSigmaESquared * k * k;\\n\\\n\tint halfWidth = int(ceil( 1.0 * sigma * k ));\\n\\n\\\n\tconst int MAX_NUM_ITERATION = 99999;\\n\\\n\tvec2 sum = vec2(0.0);\\n\\\n\tvec2 norm = vec2(0.0);\\n\\n\\\n\tfor(int cnt=0;cnt<MAX_NUM_ITERATION;cnt++){\\n\\\n\t\tif(cnt > (2*halfWidth+1)*(2*halfWidth+1)){break;}\\n\\\n\t\tint i = int(cnt / (2*halfWidth+1)) - halfWidth;\\n\\\n\t\tint j = cnt - halfWidth - int(cnt / (2*halfWidth+1)) * (2*halfWidth+1);\\n\\n\\\n\t\tfloat d = length(vec2(i,j));\\n\\\n\t\tvec2 kernel = vec2( exp( -d * d / twoSigmaESquared ), \\n\\\n\t\t\t\t\t\t\texp( -d * d / twoSigmaRSquared ));\\n\\n\\\n\t\tvec2 L = texture2D(src, (uv + vec2(i,j)) * Frag).xx;\\n\\n\\\n\t\tnorm += kernel;\\n\\\n\t\tsum += kernel * L;\\n\\\n\t}\\n\\n\\\n\tsum /= norm;\\n\\n\\\n\tfloat H = 100.0 * ((1.0 + p) * sum.x - p * sum.y);\\n\\\n\tfloat edge = ( H > epsilon )? 1.0 : 1.0 + tanh( phi * (H - epsilon));\\n\\\n\tdestColor = vec3(edge);\\n\\\n\tgl_FragColor = vec4(destColor, 1.0);\\n\\\n}\";\n\n\tLiteGraph.registerNodeType(\"texture/xDoG\", LGraphTextureXDoGFilter);\n\n\t// Texture Webcam *****************************************\n\tfunction LGraphTextureWebcam() {\n\t\tthis.addOutput(\"Webcam\", \"Texture\");\n\t\tthis.properties = { texture_name: \"\", facingMode: \"user\" };\n\t\tthis.boxcolor = \"black\";\n\t\tthis.version = 0;\n\t}\n\n\tLGraphTextureWebcam.title = \"Webcam\";\n\tLGraphTextureWebcam.desc = \"Webcam texture\";\n\n\tLGraphTextureWebcam.is_webcam_open = false;\n\n\tLGraphTextureWebcam.prototype.openStream = function() {\n\t\tif (!navigator.getUserMedia) {\n\t\t\t//console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags');\n\t\t\treturn;\n\t\t}\n\n\t\tthis._waiting_confirmation = true;\n\n\t\t// Not showing vendor prefixes.\n\t\tvar constraints = {\n\t\t\taudio: false,\n\t\t\tvideo: { facingMode: this.properties.facingMode }\n\t\t};\n\t\tnavigator.mediaDevices\n\t\t\t.getUserMedia(constraints)\n\t\t\t.then(this.streamReady.bind(this))\n\t\t\t.catch(onFailSoHard);\n\n\t\tvar that = this;\n\t\tfunction onFailSoHard(e) {\n\t\t\tLGraphTextureWebcam.is_webcam_open = false;\n\t\t\tconsole.log(\"Webcam rejected\", e);\n\t\t\tthat._webcam_stream = false;\n\t\t\tthat.boxcolor = \"red\";\n\t\t\tthat.trigger(\"stream_error\");\n\t\t}\n\t};\n\n\tLGraphTextureWebcam.prototype.closeStream = function() {\n\t\tif (this._webcam_stream) {\n\t\t\tvar tracks = this._webcam_stream.getTracks();\n\t\t\tif (tracks.length) {\n\t\t\t\tfor (var i = 0; i < tracks.length; ++i) {\n\t\t\t\t\ttracks[i].stop();\n\t\t\t\t}\n\t\t\t}\n\t\t\tLGraphTextureWebcam.is_webcam_open = false;\n\t\t\tthis._webcam_stream = null;\n\t\t\tthis._video = null;\n\t\t\tthis.boxcolor = \"black\";\n\t\t\tthis.trigger(\"stream_closed\");\n\t\t}\n\t};\n\n\tLGraphTextureWebcam.prototype.streamReady = function(localMediaStream) {\n\t\tthis._webcam_stream = localMediaStream;\n\t\t//this._waiting_confirmation = false;\n\t\tthis.boxcolor = \"green\";\n\t\tvar video = this._video;\n\t\tif (!video) {\n\t\t\tvideo = document.createElement(\"video\");\n\t\t\tvideo.autoplay = true;\n\t\t\tvideo.srcObject = localMediaStream;\n\t\t\tthis._video = video;\n\t\t\t//document.body.appendChild( video ); //debug\n\t\t\t//when video info is loaded (size and so)\n\t\t\tvideo.onloadedmetadata = function(e) {\n\t\t\t\t// Ready to go. Do some stuff.\n\t\t\t\tLGraphTextureWebcam.is_webcam_open = true;\n\t\t\t\tconsole.log(e);\n\t\t\t};\n\t\t}\n\t\tthis.trigger(\"stream_ready\", video);\n\t};\n\n\tLGraphTextureWebcam.prototype.onPropertyChanged = function(\n\t\tname,\n\t\tvalue\n\t) {\n\t\tif (name == \"facingMode\") {\n\t\t\tthis.properties.facingMode = value;\n\t\t\tthis.closeStream();\n\t\t\tthis.openStream();\n\t\t}\n\t};\n\n\tLGraphTextureWebcam.prototype.onRemoved = function() {\n\t\tif (!this._webcam_stream) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar tracks = this._webcam_stream.getTracks();\n\t\tif (tracks.length) {\n\t\t\tfor (var i = 0; i < tracks.length; ++i) {\n\t\t\t\ttracks[i].stop();\n\t\t\t}\n\t\t}\n\n\t\tthis._webcam_stream = null;\n\t\tthis._video = null;\n\t};\n\n\tLGraphTextureWebcam.prototype.onDrawBackground = function(ctx) {\n\t\tif (this.flags.collapsed || this.size[1] <= 20) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this._video) {\n\t\t\treturn;\n\t\t}\n\n\t\t//render to graph canvas\n\t\tctx.save();\n\t\tif (!ctx.webgl) {\n\t\t\t//reverse image\n\t\t\tctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]);\n\t\t} else {\n\t\t\tif (this._video_texture) {\n\t\t\t\tctx.drawImage(\n\t\t\t\t\tthis._video_texture,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\tthis.size[0],\n\t\t\t\t\tthis.size[1]\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tctx.restore();\n\t};\n\n\tLGraphTextureWebcam.prototype.onExecute = function() {\n\t\tif (this._webcam_stream == null && !this._waiting_confirmation) {\n\t\t\tthis.openStream();\n\t\t}\n\n\t\tif (!this._video || !this._video.videoWidth) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar width = this._video.videoWidth;\n\t\tvar height = this._video.videoHeight;\n\n\t\tvar temp = this._video_texture;\n\t\tif (!temp || temp.width != width || temp.height != height) {\n\t\t\tthis._video_texture = new GL.Texture(width, height, {\n\t\t\t\tformat: gl.RGB,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tthis._video_texture.uploadImage(this._video);\n\t\tthis._video_texture.version = ++this.version;\n\n\t\tif (this.properties.texture_name) {\n\t\t\tvar container = LGraphTexture.getTexturesContainer();\n\t\t\tcontainer[this.properties.texture_name] = this._video_texture;\n\t\t}\n\n\t\tthis.setOutputData(0, this._video_texture);\n\t\tfor (var i = 1; i < this.outputs.length; ++i) {\n\t\t\tif (!this.outputs[i]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tswitch (this.outputs[i].name) {\n\t\t\t\tcase \"width\":\n\t\t\t\t\tthis.setOutputData(i, this._video.videoWidth);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"height\":\n\t\t\t\t\tthis.setOutputData(i, this._video.videoHeight);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t};\n\n\tLGraphTextureWebcam.prototype.onGetOutputs = function() {\n\t\treturn [\n\t\t\t[\"width\", \"number\"],\n\t\t\t[\"height\", \"number\"],\n\t\t\t[\"stream_ready\", LiteGraph.EVENT],\n\t\t\t[\"stream_closed\", LiteGraph.EVENT],\n\t\t\t[\"stream_error\", LiteGraph.EVENT]\n\t\t];\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/webcam\", LGraphTextureWebcam);\n\n\t//from https://github.com/spite/Wagner\n\tfunction LGraphLensFX() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"f\", \"number\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tenabled: true,\n\t\t\tfactor: 1,\n\t\t\tprecision: LGraphTexture.LOW\n\t\t};\n\n\t\tthis._uniforms = { u_texture: 0, u_factor: 1 };\n\t}\n\n\tLGraphLensFX.title = \"Lens FX\";\n\tLGraphLensFX.desc = \"distortion and chromatic aberration\";\n\n\tLGraphLensFX.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphLensFX.prototype.onGetInputs = function() {\n\t\treturn [[\"enabled\", \"boolean\"]];\n\t};\n\n\tLGraphLensFX.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (\n\t\t\tthis.properties.precision === LGraphTexture.PASS_THROUGH ||\n\t\t\tthis.getInputOrProperty(\"enabled\") === false\n\t\t) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\ttemp = this._temp_texture = new GL.Texture(\n\t\t\t\ttex.width,\n\t\t\t\ttex.height,\n\t\t\t\t{ type: tex.type, format: gl.RGBA, filter: gl.LINEAR }\n\t\t\t);\n\t\t}\n\n\t\tvar shader = LGraphLensFX._shader;\n\t\tif (!shader) {\n\t\t\tshader = LGraphLensFX._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphLensFX.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar factor = this.getInputData(1);\n\t\tif (factor == null) {\n\t\t\tfactor = this.properties.factor;\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_factor = factor;\n\n\t\t//apply shader\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\ttemp.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, temp);\n\t};\n\n\tLGraphLensFX.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_factor;\\n\\\n\t\tvec2 barrelDistortion(vec2 coord, float amt) {\\n\\\n\t\t\tvec2 cc = coord - 0.5;\\n\\\n\t\t\tfloat dist = dot(cc, cc);\\n\\\n\t\t\treturn coord + cc * dist * amt;\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tfloat sat( float t )\\n\\\n\t\t{\\n\\\n\t\t\treturn clamp( t, 0.0, 1.0 );\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tfloat linterp( float t ) {\\n\\\n\t\t\treturn sat( 1.0 - abs( 2.0*t - 1.0 ) );\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tfloat remap( float t, float a, float b ) {\\n\\\n\t\t\treturn sat( (t - a) / (b - a) );\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tvec4 spectrum_offset( float t ) {\\n\\\n\t\t\tvec4 ret;\\n\\\n\t\t\tfloat lo = step(t,0.5);\\n\\\n\t\t\tfloat hi = 1.0-lo;\\n\\\n\t\t\tfloat w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\\n\\\n\t\t\tret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\\n\\\n\t\t\\n\\\n\t\t\treturn pow( ret, vec4(1.0/2.2) );\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tconst float max_distort = 2.2;\\n\\\n\t\tconst int num_iter = 12;\\n\\\n\t\tconst float reci_num_iter_f = 1.0 / float(num_iter);\\n\\\n\t\t\\n\\\n\t\tvoid main()\\n\\\n\t\t{\t\\n\\\n\t\t\tvec2 uv=v_coord;\\n\\\n\t\t\tvec4 sumcol = vec4(0.0);\\n\\\n\t\t\tvec4 sumw = vec4(0.0);\t\\n\\\n\t\t\tfor ( int i=0; i<num_iter;++i )\\n\\\n\t\t\t{\\n\\\n\t\t\t\tfloat t = float(i) * reci_num_iter_f;\\n\\\n\t\t\t\tvec4 w = spectrum_offset( t );\\n\\\n\t\t\t\tsumw += w;\\n\\\n\t\t\t\tsumcol += w * texture2D( u_texture, barrelDistortion(uv, .6 * max_distort*t * u_factor ) );\\n\\\n\t\t\t}\\n\\\n\t\t\tgl_FragColor = sumcol / sumw;\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/lensfx\", LGraphLensFX);\n\n\n\tfunction LGraphTextureFromData() {\n\t\tthis.addInput(\"in\", \"\");\n\t\tthis.properties = { precision: LGraphTexture.LOW, width: 0, height: 0, channels: 1 };\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t}\n\n\tLGraphTextureFromData.title = \"Data->Tex\";\n\tLGraphTextureFromData.desc = \"Generates or applies a curve to a texture\";\n\tLGraphTextureFromData.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureFromData.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar data = this.getInputData(0);\n\t\tif(!data)\n\t\t\treturn;\n\n\t\tvar channels = this.properties.channels;\n\t\tvar w = this.properties.width;\n\t\tvar h = this.properties.height;\n\t\tif(!w || !h)\n\t\t{\n\t\t\tw = Math.floor(data.length / channels);\n\t\t\th = 1;\n\t\t}\n\t\tvar format = gl.RGBA;\n\t\tif( channels == 3 )\n\t\t\tformat = gl.RGB;\n\t\telse if( channels == 1 )\n\t\t\tformat = gl.LUMINANCE;\n\n\t\tvar temp = this._temp_texture;\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision );\n\t\tif ( !temp || temp.width != w || temp.height != h || temp.type != type ) {\n\t\t\ttemp = this._temp_texture = new GL.Texture( w, h, { type: type, format: format, filter: gl.LINEAR } );\n\t\t}\n\n\t\ttemp.uploadData( data );\n\t\tthis.setOutputData(0, temp);\n\t}\n\n\tLiteGraph.registerNodeType(\"texture/fromdata\", LGraphTextureFromData);\n\n\t//applies a curve (or generates one)\n\tfunction LGraphTextureCurve() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = { precision: LGraphTexture.LOW, split_channels: false };\n\t\tthis._values = new Uint8Array(256*4);\n\t\tthis._values.fill(255);\n\t\tthis._curve_texture = null;\n\t\tthis._uniforms = { u_texture: 0, u_curve: 1, u_range: 1.0 };\n\t\tthis._must_update = true;\n\t\tthis._points = {\n\t\t\tRGB: [[0,0],[1,1]],\n\t\t\tR: [[0,0],[1,1]],\n\t\t\tG: [[0,0],[1,1]],\n\t\t\tB: [[0,0],[1,1]]\n\t\t};\n\t\tthis.curve_editor = null;\n\t\tthis.addWidget(\"toggle\",\"Split Channels\",false,\"split_channels\");\n\t\tthis.addWidget(\"combo\",\"Channel\",\"RGB\",{ values:[\"RGB\",\"R\",\"G\",\"B\"]});\n\t\tthis.curve_offset = 68;\n\t\tthis.size = [ 240, 160 ];\n\t}\n\n\tLGraphTextureCurve.title = \"Curve\";\n\tLGraphTextureCurve.desc = \"Generates or applies a curve to a texture\";\n\tLGraphTextureCurve.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureCurve.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\n\t\tvar temp = this._temp_texture;\n\t\tif(!tex) //generate one texture, nothing else\n\t\t{\n\t\t\tif(this._must_update || !this._curve_texture )\n\t\t\t\tthis.updateCurve();\n\t\t\tthis.setOutputData(0, this._curve_texture);\n\t\t\treturn;\n\t\t}\n\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision, tex );\n\t\t\n\t\t//apply curve to input texture\n\t\tif ( !temp || temp.type != type || temp.width != tex.width || temp.height != tex.height || temp.format != tex.format)\n\t\t\ttemp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR } );\n\n\t\tvar shader = LGraphTextureCurve._shader;\n\t\tif (!shader) {\n\t\t\tshader = LGraphTextureCurve._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureCurve.pixel_shader );\n\t\t}\n\n\t\tif(this._must_update || !this._curve_texture )\n\t\t\tthis.updateCurve();\n\n\t\tvar uniforms = this._uniforms;\n\t\tvar curve_texture = this._curve_texture;\n\n\t\t//apply shader\n\t\ttemp.drawTo(function() {\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\ttex.bind(0);\n\t\t\tcurve_texture.bind(1);\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, temp);\n\t}\n\n\tLGraphTextureCurve.prototype.sampleCurve = function(f,points)\n\t{\n\t\tvar points = points || this._points.RGB;\n\t\tif(!points)\n\t\t\treturn;\n\t\tfor(var i = 0; i < points.length - 1; ++i)\n\t\t{\n\t\t\tvar p = points[i];\n\t\t\tvar pn = points[i+1];\n\t\t\tif(pn[0] < f)\n\t\t\t\tcontinue;\n\t\t\tvar r = (pn[0] - p[0]);\n\t\t\tif( Math.abs(r) < 0.00001 )\n\t\t\t\treturn p[1];\n\t\t\tvar local_f = (f - p[0]) / r;\n\t\t\treturn p[1] * (1.0 - local_f) + pn[1] * local_f;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tLGraphTextureCurve.prototype.updateCurve = function()\n\t{\n\t\tvar values = this._values;\n\t\tvar num = values.length / 4;\n\t\tvar split = this.properties.split_channels;\n\t\tfor(var i = 0; i < num; ++i)\n\t\t{\n\t\t\tif(split)\n\t\t\t{\n\t\t\t\tvalues[i*4] = clamp( this.sampleCurve(i/num,this._points.R)*255,0,255);\n\t\t\t\tvalues[i*4+1] = clamp( this.sampleCurve(i/num,this._points.G)*255,0,255);\n\t\t\t\tvalues[i*4+2] = clamp( this.sampleCurve(i/num,this._points.B)*255,0,255);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvar v = this.sampleCurve(i/num);//sample curve\n\t\t\t\tvalues[i*4] = values[i*4+1] = values[i*4+2] = clamp(v*255,0,255);\n\t\t\t}\n\t\t\tvalues[i*4+3] = 255; //alpha fixed\n\t\t}\n\t\tif(!this._curve_texture)\n\t\t\tthis._curve_texture = new GL.Texture(256,1,{ format: gl.RGBA, magFilter: gl.LINEAR, wrap: gl.CLAMP_TO_EDGE });\n\t\tthis._curve_texture.uploadData(values,null,true);\n\t}\n\n\tLGraphTextureCurve.prototype.onSerialize = function(o)\n\t{\n\t\tvar curves = {};\n\t\tfor(var i in this._points)\n\t\t\tcurves[i] = this._points[i].concat();\n\t\to.curves = curves;\n\t}\n\n\tLGraphTextureCurve.prototype.onConfigure = function(o)\n\t{\n\t\tthis._points = o.curves;\n\t\tif(this.curve_editor)\n\t\t\tcurve_editor.points = this._points;\n\t\tthis._must_update = true;\n\t}\n\n\tLGraphTextureCurve.prototype.onMouseDown = function(e, localpos, graphcanvas)\n\t{\n\t\tif(this.curve_editor)\n\t\t{\n\t\t\tvar r = this.curve_editor.onMouseDown([localpos[0],localpos[1]-this.curve_offset], graphcanvas);\n\t\t\tif(r)\n\t\t\t\tthis.captureInput(true);\n\t\t\treturn r;\n\t\t}\n\t}\n\n\tLGraphTextureCurve.prototype.onMouseMove = function(e, localpos, graphcanvas)\n\t{\n\t\tif(this.curve_editor)\n\t\t\treturn this.curve_editor.onMouseMove([localpos[0],localpos[1]-this.curve_offset], graphcanvas);\n\t}\n\n\tLGraphTextureCurve.prototype.onMouseUp = function(e, localpos, graphcanvas)\n\t{\n\t\tif(this.curve_editor)\n\t\t\treturn this.curve_editor.onMouseUp([localpos[0],localpos[1]-this.curve_offset], graphcanvas);\n\t\tthis.captureInput(false);\n\t}\n\n\tLGraphTextureCurve.channel_line_colors = { \"RGB\":\"#666\",\"R\":\"#F33\",\"G\":\"#3F3\",\"B\":\"#33F\" };\n\n\tLGraphTextureCurve.prototype.onDrawBackground = function(ctx, graphcanvas)\n\t{\n\t\tif(this.flags.collapsed)\n\t\t\treturn;\n\n\t\tif(!this.curve_editor)\n\t\t\tthis.curve_editor = new LiteGraph.CurveEditor(this._points.R);\n\t\tctx.save();\n\t\tctx.translate(0,this.curve_offset);\n\t\tvar channel = this.widgets[1].value;\n\n\t\tif(this.properties.split_channels)\n\t\t{\n\t\t\tif(channel == \"RGB\")\n\t\t\t{\n\t\t\t\tthis.widgets[1].value = channel = \"R\";\n\t\t\t\tthis.widgets[1].disabled = false;\n\t\t\t}\n\t\t\tthis.curve_editor.points = this._points.R;\n\t\t\tthis.curve_editor.draw( ctx, [this.size[0],this.size[1] - this.curve_offset], graphcanvas, \"#111\", LGraphTextureCurve.channel_line_colors.R, true );\n\t\t\tctx.globalCompositeOperation = \"lighten\";\n\t\t\tthis.curve_editor.points = this._points.G;\n\t\t\tthis.curve_editor.draw( ctx, [this.size[0],this.size[1] - this.curve_offset], graphcanvas, null, LGraphTextureCurve.channel_line_colors.G, true );\n\t\t\tthis.curve_editor.points = this._points.B;\n\t\t\tthis.curve_editor.draw( ctx, [this.size[0],this.size[1] - this.curve_offset], graphcanvas, null, LGraphTextureCurve.channel_line_colors.B, true );\n\t\t\tctx.globalCompositeOperation = \"source-over\";\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis.widgets[1].value = channel = \"RGB\";\n\t\t\tthis.widgets[1].disabled = true;\n\t\t}\n\n\t\tthis.curve_editor.points = this._points[channel];\n\t\tthis.curve_editor.draw( ctx, [this.size[0],this.size[1] - this.curve_offset], graphcanvas, this.properties.split_channels ? null : \"#111\", LGraphTextureCurve.channel_line_colors[channel]  );\n\t\tctx.restore();\n\t}\n\n\tLGraphTextureCurve.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform sampler2D u_curve;\\n\\\n\t\tuniform float u_range;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = texture2D( u_texture, v_coord ) * u_range;\\n\\\n\t\t\tcolor.x = texture2D( u_curve, vec2( color.x, 0.5 ) ).x;\\n\\\n\t\t\tcolor.y = texture2D( u_curve, vec2( color.y, 0.5 ) ).y;\\n\\\n\t\t\tcolor.z = texture2D( u_curve, vec2( color.z, 0.5 ) ).z;\\n\\\n\t\t\t//color.w = texture2D( u_curve, vec2( color.w, 0.5 ) ).w;\\n\\\n\t\t\tgl_FragColor = color;\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/curve\", LGraphTextureCurve);\n\n\t//simple exposition, but plan to expand it to support different gamma curves\n\tfunction LGraphExposition() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"exp\", \"number\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = { exposition: 1, precision: LGraphTexture.LOW };\n\t\tthis._uniforms = { u_texture: 0, u_exposition: 1 };\n\t}\n\n\tLGraphExposition.title = \"Exposition\";\n\tLGraphExposition.desc = \"Controls texture exposition\";\n\n\tLGraphExposition.widgets_info = {\n\t\texposition: { widget: \"slider\", min: 0, max: 3 },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphExposition.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar temp = this._temp_texture;\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\ttemp = this._temp_texture = new GL.Texture(\n\t\t\t\ttex.width,\n\t\t\t\ttex.height,\n\t\t\t\t{ type: tex.type, format: gl.RGBA, filter: gl.LINEAR }\n\t\t\t);\n\t\t}\n\n\t\tvar shader = LGraphExposition._shader;\n\t\tif (!shader) {\n\t\t\tshader = LGraphExposition._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphExposition.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tvar exp = this.properties.exposition;\n\t\tvar exp_input = this.getInputData(1);\n\t\tif (exp_input != null) {\n\t\t\texp = this.properties.exposition = exp_input;\n\t\t}\n\t\tvar uniforms = this._uniforms;\n\n\t\t//apply shader\n\t\ttemp.drawTo(function() {\n\t\t\tgl.disable(gl.DEPTH_TEST);\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, temp);\n\t};\n\n\tLGraphExposition.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_exposition;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = texture2D( u_texture, v_coord );\\n\\\n\t\t\tgl_FragColor = vec4( color.xyz * u_exposition, color.a );\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/exposition\", LGraphExposition);\n\n\tfunction LGraphToneMapping() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\t\tthis.addInput(\"avg\", \"number,Texture\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tenabled: true,\n\t\t\tscale: 1,\n\t\t\tgamma: 1,\n\t\t\taverage_lum: 1,\n\t\t\tlum_white: 1,\n\t\t\tprecision: LGraphTexture.LOW\n\t\t};\n\n\t\tthis._uniforms = {\n\t\t\tu_texture: 0,\n\t\t\tu_lumwhite2: 1,\n\t\t\tu_igamma: 1,\n\t\t\tu_scale: 1,\n\t\t\tu_average_lum: 1\n\t\t};\n\t}\n\n\tLGraphToneMapping.title = \"Tone Mapping\";\n\tLGraphToneMapping.desc =\n\t\t\"Applies Tone Mapping to convert from high to low\";\n\n\tLGraphToneMapping.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphToneMapping.prototype.onGetInputs = function() {\n\t\treturn [[\"enabled\", \"boolean\"]];\n\t};\n\n\tLGraphToneMapping.prototype.onExecute = function() {\n\t\tvar tex = this.getInputData(0);\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tif (\n\t\t\tthis.properties.precision === LGraphTexture.PASS_THROUGH ||\n\t\t\tthis.getInputOrProperty(\"enabled\") === false\n\t\t) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tvar temp = this._temp_texture;\n\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != tex.width ||\n\t\t\ttemp.height != tex.height ||\n\t\t\ttemp.type != tex.type\n\t\t) {\n\t\t\ttemp = this._temp_texture = new GL.Texture(\n\t\t\t\ttex.width,\n\t\t\t\ttex.height,\n\t\t\t\t{ type: tex.type, format: gl.RGBA, filter: gl.LINEAR }\n\t\t\t);\n\t\t}\n\n\t\tvar avg = this.getInputData(1);\n\t\tif (avg == null) {\n\t\t\tavg = this.properties.average_lum;\n\t\t}\n\n\t\tvar uniforms = this._uniforms;\n\t\tvar shader = null;\n\n\t\tif (avg.constructor === Number) {\n\t\t\tthis.properties.average_lum = avg;\n\t\t\tuniforms.u_average_lum = this.properties.average_lum;\n\t\t\tshader = LGraphToneMapping._shader;\n\t\t\tif (!shader) {\n\t\t\t\tshader = LGraphToneMapping._shader = new GL.Shader(\n\t\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\tLGraphToneMapping.pixel_shader\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (avg.constructor === GL.Texture) {\n\t\t\tuniforms.u_average_texture = avg.bind(1);\n\t\t\tshader = LGraphToneMapping._shader_texture;\n\t\t\tif (!shader) {\n\t\t\t\tshader = LGraphToneMapping._shader_texture = new GL.Shader(\n\t\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\t\tLGraphToneMapping.pixel_shader,\n\t\t\t\t\t{ AVG_TEXTURE: \"\" }\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tuniforms.u_lumwhite2 =\n\t\t\tthis.properties.lum_white * this.properties.lum_white;\n\t\tuniforms.u_scale = this.properties.scale;\n\t\tuniforms.u_igamma = 1 / this.properties.gamma;\n\n\t\t//apply shader\n\t\tgl.disable(gl.DEPTH_TEST);\n\t\ttemp.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, this._temp_texture);\n\t};\n\n\tLGraphToneMapping.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform float u_scale;\\n\\\n\t\t#ifdef AVG_TEXTURE\\n\\\n\t\t\tuniform sampler2D u_average_texture;\\n\\\n\t\t#else\\n\\\n\t\t\tuniform float u_average_lum;\\n\\\n\t\t#endif\\n\\\n\t\tuniform float u_lumwhite2;\\n\\\n\t\tuniform float u_igamma;\\n\\\n\t\tvec3 RGB2xyY (vec3 rgb)\\n\\\n\t\t{\\n\\\n\t\t\t const mat3 RGB2XYZ = mat3(0.4124, 0.3576, 0.1805,\\n\\\n\t\t\t\t\t\t\t\t\t   0.2126, 0.7152, 0.0722,\\n\\\n\t\t\t\t\t\t\t\t\t   0.0193, 0.1192, 0.9505);\\n\\\n\t\t\tvec3 XYZ = RGB2XYZ * rgb;\\n\\\n\t\t\t\\n\\\n\t\t\tfloat f = (XYZ.x + XYZ.y + XYZ.z);\\n\\\n\t\t\treturn vec3(XYZ.x / f,\\n\\\n\t\t\t\t\t\tXYZ.y / f,\\n\\\n\t\t\t\t\t\tXYZ.y);\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec4 color = texture2D( u_texture, v_coord );\\n\\\n\t\t\tvec3 rgb = color.xyz;\\n\\\n\t\t\tfloat average_lum = 0.0;\\n\\\n\t\t\t#ifdef AVG_TEXTURE\\n\\\n\t\t\t\tvec3 pixel = texture2D(u_average_texture,vec2(0.5)).xyz;\\n\\\n\t\t\t\taverage_lum = (pixel.x + pixel.y + pixel.z) / 3.0;\\n\\\n\t\t\t#else\\n\\\n\t\t\t\taverage_lum = u_average_lum;\\n\\\n\t\t\t#endif\\n\\\n\t\t\t//Ld - this part of the code is the same for both versions\\n\\\n\t\t\tfloat lum = dot(rgb, vec3(0.2126, 0.7152, 0.0722));\\n\\\n\t\t\tfloat L = (u_scale / average_lum) * lum;\\n\\\n\t\t\tfloat Ld = (L * (1.0 + L / u_lumwhite2)) / (1.0 + L);\\n\\\n\t\t\t//first\\n\\\n\t\t\t//vec3 xyY = RGB2xyY(rgb);\\n\\\n\t\t\t//xyY.z *= Ld;\\n\\\n\t\t\t//rgb = xyYtoRGB(xyY);\\n\\\n\t\t\t//second\\n\\\n\t\t\trgb = (rgb / lum) * Ld;\\n\\\n\t\t\trgb = max(rgb,vec3(0.001));\\n\\\n\t\t\trgb = pow( rgb, vec3( u_igamma ) );\\n\\\n\t\t\tgl_FragColor = vec4( rgb, color.a );\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/tonemapping\", LGraphToneMapping);\n\n\tfunction LGraphTexturePerlin() {\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\twidth: 512,\n\t\t\theight: 512,\n\t\t\tseed: 0,\n\t\t\tpersistence: 0.1,\n\t\t\toctaves: 8,\n\t\t\tscale: 1,\n\t\t\toffset: [0, 0],\n\t\t\tamplitude: 1,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t\tthis._key = 0;\n\t\tthis._texture = null;\n\t\tthis._uniforms = {\n\t\t\tu_persistence: 0.1,\n\t\t\tu_seed: 0,\n\t\t\tu_offset: vec2.create(),\n\t\t\tu_scale: 1,\n\t\t\tu_viewport: vec2.create()\n\t\t};\n\t}\n\n\tLGraphTexturePerlin.title = \"Perlin\";\n\tLGraphTexturePerlin.desc = \"Generates a perlin noise texture\";\n\n\tLGraphTexturePerlin.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES },\n\t\twidth: { type: \"number\", precision: 0, step: 1 },\n\t\theight: { type: \"number\", precision: 0, step: 1 },\n\t\toctaves: { type: \"number\", precision: 0, step: 1, min: 1, max: 50 }\n\t};\n\n\tLGraphTexturePerlin.prototype.onGetInputs = function() {\n\t\treturn [\n\t\t\t[\"seed\", \"number\"],\n\t\t\t[\"persistence\", \"number\"],\n\t\t\t[\"octaves\", \"number\"],\n\t\t\t[\"scale\", \"number\"],\n\t\t\t[\"amplitude\", \"number\"],\n\t\t\t[\"offset\", \"vec2\"]\n\t\t];\n\t};\n\n\tLGraphTexturePerlin.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar w = this.properties.width | 0;\n\t\tvar h = this.properties.height | 0;\n\t\tif (w == 0) {\n\t\t\tw = gl.viewport_data[2];\n\t\t} //0 means default\n\t\tif (h == 0) {\n\t\t\th = gl.viewport_data[3];\n\t\t} //0 means default\n\t\tvar type = LGraphTexture.getTextureType(this.properties.precision);\n\n\t\tvar temp = this._texture;\n\t\tif (\n\t\t\t!temp ||\n\t\t\ttemp.width != w ||\n\t\t\ttemp.height != h ||\n\t\t\ttemp.type != type\n\t\t) {\n\t\t\ttemp = this._texture = new GL.Texture(w, h, {\n\t\t\t\ttype: type,\n\t\t\t\tformat: gl.RGB,\n\t\t\t\tfilter: gl.LINEAR\n\t\t\t});\n\t\t}\n\n\t\tvar persistence = this.getInputOrProperty(\"persistence\");\n\t\tvar octaves = this.getInputOrProperty(\"octaves\");\n\t\tvar offset = this.getInputOrProperty(\"offset\");\n\t\tvar scale = this.getInputOrProperty(\"scale\");\n\t\tvar amplitude = this.getInputOrProperty(\"amplitude\");\n\t\tvar seed = this.getInputOrProperty(\"seed\");\n\n\t\t//reusing old texture\n\t\tvar key =\n\t\t\t\"\" +\n\t\t\tw +\n\t\t\th +\n\t\t\ttype +\n\t\t\tpersistence +\n\t\t\toctaves +\n\t\t\tscale +\n\t\t\tseed +\n\t\t\toffset[0] +\n\t\t\toffset[1] +\n\t\t\tamplitude;\n\t\tif (key == this._key) {\n\t\t\tthis.setOutputData(0, temp);\n\t\t\treturn;\n\t\t}\n\t\tthis._key = key;\n\n\t\t//gather uniforms\n\t\tvar uniforms = this._uniforms;\n\t\tuniforms.u_persistence = persistence;\n\t\tuniforms.u_octaves = octaves;\n\t\tuniforms.u_offset.set(offset);\n\t\tuniforms.u_scale = scale;\n\t\tuniforms.u_amplitude = amplitude;\n\t\tuniforms.u_seed = seed * 128;\n\t\tuniforms.u_viewport[0] = w;\n\t\tuniforms.u_viewport[1] = h;\n\n\t\t//render\n\t\tvar shader = LGraphTexturePerlin._shader;\n\t\tif (!shader) {\n\t\t\tshader = LGraphTexturePerlin._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTexturePerlin.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\ttemp.drawTo(function() {\n\t\t\tshader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());\n\t\t});\n\n\t\tthis.setOutputData(0, temp);\n\t};\n\n\tLGraphTexturePerlin.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform vec2 u_offset;\\n\\\n\t\tuniform float u_scale;\\n\\\n\t\tuniform float u_persistence;\\n\\\n\t\tuniform int u_octaves;\\n\\\n\t\tuniform float u_amplitude;\\n\\\n\t\tuniform vec2 u_viewport;\\n\\\n\t\tuniform float u_seed;\\n\\\n\t\t#define M_PI 3.14159265358979323846\\n\\\n\t\t\\n\\\n\t\tfloat rand(vec2 c){\treturn fract(sin(dot(c.xy ,vec2( 12.9898 + u_seed,78.233 + u_seed))) * 43758.5453); }\\n\\\n\t\t\\n\\\n\t\tfloat noise(vec2 p, float freq ){\\n\\\n\t\t\tfloat unit = u_viewport.x/freq;\\n\\\n\t\t\tvec2 ij = floor(p/unit);\\n\\\n\t\t\tvec2 xy = mod(p,unit)/unit;\\n\\\n\t\t\t//xy = 3.*xy*xy-2.*xy*xy*xy;\\n\\\n\t\t\txy = .5*(1.-cos(M_PI*xy));\\n\\\n\t\t\tfloat a = rand((ij+vec2(0.,0.)));\\n\\\n\t\t\tfloat b = rand((ij+vec2(1.,0.)));\\n\\\n\t\t\tfloat c = rand((ij+vec2(0.,1.)));\\n\\\n\t\t\tfloat d = rand((ij+vec2(1.,1.)));\\n\\\n\t\t\tfloat x1 = mix(a, b, xy.x);\\n\\\n\t\t\tfloat x2 = mix(c, d, xy.x);\\n\\\n\t\t\treturn mix(x1, x2, xy.y);\\n\\\n\t\t}\\n\\\n\t\t\\n\\\n\t\tfloat pNoise(vec2 p, int res){\\n\\\n\t\t\tfloat persistance = u_persistence;\\n\\\n\t\t\tfloat n = 0.;\\n\\\n\t\t\tfloat normK = 0.;\\n\\\n\t\t\tfloat f = 4.;\\n\\\n\t\t\tfloat amp = 1.0;\\n\\\n\t\t\tint iCount = 0;\\n\\\n\t\t\tfor (int i = 0; i<50; i++){\\n\\\n\t\t\t\tn+=amp*noise(p, f);\\n\\\n\t\t\t\tf*=2.;\\n\\\n\t\t\t\tnormK+=amp;\\n\\\n\t\t\t\tamp*=persistance;\\n\\\n\t\t\t\tif (iCount >= res)\\n\\\n\t\t\t\t\tbreak;\\n\\\n\t\t\t\tiCount++;\\n\\\n\t\t\t}\\n\\\n\t\t\tfloat nf = n/normK;\\n\\\n\t\t\treturn nf*nf*nf*nf;\\n\\\n\t\t}\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec2 uv = v_coord * u_scale * u_viewport + u_offset * u_scale;\\n\\\n\t\t\tvec4 color = vec4( pNoise( uv, u_octaves ) * u_amplitude );\\n\\\n\t\t\tgl_FragColor = color;\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/perlin\", LGraphTexturePerlin);\n\n\tfunction LGraphTextureCanvas2D() {\n\t\tthis.addInput(\"v\");\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tcode: LGraphTextureCanvas2D.default_code,\n\t\t\twidth: 512,\n\t\t\theight: 512,\n\t\t\tclear: true,\n\t\t\tprecision: LGraphTexture.DEFAULT,\n\t\t\tuse_html_canvas: false\n\t\t};\n\t\tthis._func = null;\n\t\tthis._temp_texture = null;\n\t\tthis.compileCode();\n\t}\n\n\tLGraphTextureCanvas2D.title = \"Canvas2D\";\n\tLGraphTextureCanvas2D.desc = \"Executes Canvas2D code inside a texture or the viewport.\";\n\tLGraphTextureCanvas2D.help = \"Set width and height to 0 to match viewport size.\";\n\n\tLGraphTextureCanvas2D.default_code = \"//vars: canvas,ctx,time\\nctx.fillStyle='red';\\nctx.fillRect(0,0,50,50);\\n\";\n\n\tLGraphTextureCanvas2D.widgets_info = {\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES },\n\t\tcode: { type: \"code\" },\n\t\twidth: { type: \"number\", precision: 0, step: 1 },\n\t\theight: { type: \"number\", precision: 0, step: 1 }\n\t};\n\n\tLGraphTextureCanvas2D.prototype.onPropertyChanged = function( name, value ) {\n\t\tif (name == \"code\" )\n\t\t\tthis.compileCode( value );\n\t}\n\t\n\tLGraphTextureCanvas2D.prototype.compileCode = function( code ) {\n\t\tthis._func = null;\n\t\tif( !LiteGraph.allow_scripts )\n\t\t\treturn;\n\n\t\ttry {\n\t\t\tthis._func = new Function( \"canvas\", \"ctx\", \"time\", \"script\",\"v\", code );\n\t\t\tthis.boxcolor = \"#00FF00\";\n\t\t} catch (err) {\n\t\t\tthis.boxcolor = \"#FF0000\";\n\t\t\tconsole.error(\"Error parsing script\");\n\t\t\tconsole.error(err);\n\t\t}\n\t};\n\n\tLGraphTextureCanvas2D.prototype.onExecute = function() {\n\t\tvar func = this._func;\n\t\tif (!func || !this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t}\n\t\tthis.executeDraw( func );\n\t}\n\n\tLGraphTextureCanvas2D.prototype.executeDraw = function( func_context ) {\n\n\t\tvar width = this.properties.width || gl.canvas.width;\n\t\tvar height = this.properties.height || gl.canvas.height;\n\t\tvar temp = this._temp_texture;\n\t\tvar type = LGraphTexture.getTextureType( this.properties.precision );\n\t\tif (!temp || temp.width != width || temp.height != height || temp.type != type ) {\n\t\t\ttemp = this._temp_texture = new GL.Texture(width, height, {\n\t\t\t\tformat: gl.RGBA,\n\t\t\t\tfilter: gl.LINEAR,\n\t\t\t\ttype: type\n\t\t\t});\n\t\t}\n\n\t\tvar v = this.getInputData(0);\n\n\t\tvar properties = this.properties;\n\t\tvar that = this;\n\t\tvar time = this.graph.getTime();\n\t\tvar ctx = gl;\n\t\tvar canvas = gl.canvas;\n\t\tif( this.properties.use_html_canvas || !global.enableWebGLCanvas )\n\t\t{\n\t\t\tif(!this._canvas)\n\t\t\t{\n\t\t\t\tcanvas = this._canvas = createCanvas(width.height);\n\t\t\t\tctx = this._ctx = canvas.getContext(\"2d\");\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tcanvas = this._canvas;\n\t\t\t\tctx = this._ctx;\n\t\t\t}\n\t\t\tcanvas.width = width;\n\t\t\tcanvas.height = height;\n\t\t}\n\n\t\tif(ctx == gl) //using Canvas2DtoWebGL\n\t\t\ttemp.drawTo(function() {\n\t\t\t\tgl.start2D();\n\t\t\t\tif(properties.clear)\n\t\t\t\t{\n\t\t\t\t\tgl.clearColor(0,0,0,0);\n\t\t\t\t\tgl.clear( gl.COLOR_BUFFER_BIT );\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tif (func_context.draw) {\n\t\t\t\t\t\tfunc_context.draw.call(that, canvas, ctx, time, func_context, v);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfunc_context.call(that, canvas, ctx, time, func_context,v);\n\t\t\t\t\t}\n\t\t\t\t\tthat.boxcolor = \"#00FF00\";\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthat.boxcolor = \"#FF0000\";\n\t\t\t\t\tconsole.error(\"Error executing script\");\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t}\n\t\t\t\tgl.finish2D();\n\t\t\t});\n\t\telse //rendering to offscreen canvas and uploading to texture\n\t\t{\n\t\t\tif(properties.clear)\n\t\t\t\tctx.clearRect(0,0,canvas.width,canvas.height);\n\n\t\t\ttry {\n\t\t\t\tif (func_context.draw) {\n\t\t\t\t\tfunc_context.draw.call(this, canvas, ctx, time, func_context, v);\n\t\t\t\t} else {\n\t\t\t\t\tfunc_context.call(this, canvas, ctx, time, func_context,v);\n\t\t\t\t}\n\t\t\t\tthis.boxcolor = \"#00FF00\";\n\t\t\t} catch (err) {\n\t\t\t\tthis.boxcolor = \"#FF0000\";\n\t\t\t\tconsole.error(\"Error executing script\");\n\t\t\t\tconsole.error(err);\n\t\t\t}\n\t\t\ttemp.uploadImage( canvas );\n\t\t}\n\n\t\tthis.setOutputData(0, temp);\n\t};\n\n\tLiteGraph.registerNodeType(\"texture/canvas2D\", LGraphTextureCanvas2D);\n\n\t// To do chroma keying *****************\n\n\tfunction LGraphTextureMatte() {\n\t\tthis.addInput(\"in\", \"Texture\");\n\n\t\tthis.addOutput(\"out\", \"Texture\");\n\t\tthis.properties = {\n\t\t\tkey_color: vec3.fromValues(0, 1, 0),\n\t\t\tthreshold: 0.8,\n\t\t\tslope: 0.2,\n\t\t\tprecision: LGraphTexture.DEFAULT\n\t\t};\n\t}\n\n\tLGraphTextureMatte.title = \"Matte\";\n\tLGraphTextureMatte.desc = \"Extracts background\";\n\n\tLGraphTextureMatte.widgets_info = {\n\t\tkey_color: { widget: \"color\" },\n\t\tprecision: { widget: \"combo\", values: LGraphTexture.MODE_VALUES }\n\t};\n\n\tLGraphTextureMatte.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0)) {\n\t\t\treturn;\n\t\t} //saves work\n\n\t\tvar tex = this.getInputData(0);\n\n\t\tif (this.properties.precision === LGraphTexture.PASS_THROUGH) {\n\t\t\tthis.setOutputData(0, tex);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tex) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._tex = LGraphTexture.getTargetTexture(\n\t\t\ttex,\n\t\t\tthis._tex,\n\t\t\tthis.properties.precision\n\t\t);\n\n\t\tgl.disable(gl.BLEND);\n\t\tgl.disable(gl.DEPTH_TEST);\n\n\t\tif (!this._uniforms) {\n\t\t\tthis._uniforms = {\n\t\t\t\tu_texture: 0,\n\t\t\t\tu_key_color: this.properties.key_color,\n\t\t\t\tu_threshold: 1,\n\t\t\t\tu_slope: 1\n\t\t\t};\n\t\t}\n\t\tvar uniforms = this._uniforms;\n\n\t\tvar mesh = Mesh.getScreenQuad();\n\t\tvar shader = LGraphTextureMatte._shader;\n\t\tif (!shader) {\n\t\t\tshader = LGraphTextureMatte._shader = new GL.Shader(\n\t\t\t\tGL.Shader.SCREEN_VERTEX_SHADER,\n\t\t\t\tLGraphTextureMatte.pixel_shader\n\t\t\t);\n\t\t}\n\n\t\tuniforms.u_key_color = this.properties.key_color;\n\t\tuniforms.u_threshold = this.properties.threshold;\n\t\tuniforms.u_slope = this.properties.slope;\n\n\t\tthis._tex.drawTo(function() {\n\t\t\ttex.bind(0);\n\t\t\tshader.uniforms(uniforms).draw(mesh);\n\t\t});\n\n\t\tthis.setOutputData(0, this._tex);\n\t};\n\n\tLGraphTextureMatte.pixel_shader =\n\t\t\"precision highp float;\\n\\\n\t\tvarying vec2 v_coord;\\n\\\n\t\tuniform sampler2D u_texture;\\n\\\n\t\tuniform vec3 u_key_color;\\n\\\n\t\tuniform float u_threshold;\\n\\\n\t\tuniform float u_slope;\\n\\\n\t\t\\n\\\n\t\tvoid main() {\\n\\\n\t\t\tvec3 color = texture2D( u_texture, v_coord ).xyz;\\n\\\n\t\t\tfloat diff = length( normalize(color) - normalize(u_key_color) );\\n\\\n\t\t\tfloat edge = u_threshold * (1.0 - u_slope);\\n\\\n\t\t\tfloat alpha = smoothstep( edge, u_threshold, diff);\\n\\\n\t\t\tgl_FragColor = vec4( color, alpha );\\n\\\n\t\t}\";\n\n\tLiteGraph.registerNodeType(\"texture/matte\", LGraphTextureMatte);\n\n\t//***********************************\n\tfunction LGraphCubemapToTexture2D() {\n\t\tthis.addInput(\"in\", \"texture\");\n\t\tthis.addInput(\"yaw\", \"number\");\n\t\tthis.addOutput(\"out\", \"texture\");\n\t\tthis.properties = { yaw: 0 };\n\t}\n\n\tLGraphCubemapToTexture2D.title = \"CubemapToTexture2D\";\n\tLGraphCubemapToTexture2D.desc = \"Transforms a CUBEMAP texture into a TEXTURE2D in Polar Representation\";\n\n\tLGraphCubemapToTexture2D.prototype.onExecute = function() {\n\t\tif (!this.isOutputConnected(0))\n\t\t\treturn;\n\n\t\tvar tex = this.getInputData(0);\n\t\tif ( !tex || tex.texture_type != GL.TEXTURE_CUBE_MAP )\n\t\t\treturn;\n\t\tif( this._last_tex && ( this._last_tex.height != tex.height || this._last_tex.type != tex.type ))\n\t\t\tthis._last_tex = null;\n\t\tvar yaw = this.getInputOrProperty(\"yaw\");\n\t\tthis._last_tex = GL.Texture.cubemapToTexture2D( tex, tex.height, this._last_tex, true, yaw );\n\t\tthis.setOutputData( 0, this._last_tex );\n\t};\n\n\tLiteGraph.registerNodeType( \"texture/cubemapToTexture2D\", LGraphCubemapToTexture2D );\n})(this);\n"
  },
  {
    "path": "src/nodes/graphics.js",
    "content": "(function(global) {\n    var LiteGraph = global.LiteGraph;\n\n    function GraphicsPlot() {\n        this.addInput(\"A\", \"Number\");\n        this.addInput(\"B\", \"Number\");\n        this.addInput(\"C\", \"Number\");\n        this.addInput(\"D\", \"Number\");\n\n        this.values = [[], [], [], []];\n        this.properties = { scale: 2 };\n    }\n\n    GraphicsPlot.title = \"Plot\";\n    GraphicsPlot.desc = \"Plots data over time\";\n    GraphicsPlot.colors = [\"#FFF\", \"#F99\", \"#9F9\", \"#99F\"];\n\n    GraphicsPlot.prototype.onExecute = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        var size = this.size;\n\n        for (var i = 0; i < 4; ++i) {\n            var v = this.getInputData(i);\n            if (v == null) {\n                continue;\n            }\n            var values = this.values[i];\n            values.push(v);\n            if (values.length > size[0]) {\n                values.shift();\n            }\n        }\n    };\n\n    GraphicsPlot.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        var size = this.size;\n\n        var scale = (0.5 * size[1]) / this.properties.scale;\n        var colors = GraphicsPlot.colors;\n        var offset = size[1] * 0.5;\n\n        ctx.fillStyle = \"#000\";\n        ctx.fillRect(0, 0, size[0], size[1]);\n        ctx.strokeStyle = \"#555\";\n        ctx.beginPath();\n        ctx.moveTo(0, offset);\n        ctx.lineTo(size[0], offset);\n        ctx.stroke();\n\n        if (this.inputs) {\n            for (var i = 0; i < 4; ++i) {\n                var values = this.values[i];\n                if (!this.inputs[i] || !this.inputs[i].link) {\n                    continue;\n                }\n                ctx.strokeStyle = colors[i];\n                ctx.beginPath();\n                var v = values[0] * scale * -1 + offset;\n                ctx.moveTo(0, clamp(v, 0, size[1]));\n                for (var j = 1; j < values.length && j < size[0]; ++j) {\n                    var v = values[j] * scale * -1 + offset;\n                    ctx.lineTo(j, clamp(v, 0, size[1]));\n                }\n                ctx.stroke();\n            }\n        }\n    };\n\n    LiteGraph.registerNodeType(\"graphics/plot\", GraphicsPlot);\n\n    function GraphicsImage() {\n        this.addOutput(\"frame\", \"image\");\n        this.properties = { url: \"\" };\n    }\n\n    GraphicsImage.title = \"Image\";\n    GraphicsImage.desc = \"Image loader\";\n    GraphicsImage.widgets = [{ name: \"load\", text: \"Load\", type: \"button\" }];\n\n    GraphicsImage.supported_extensions = [\"jpg\", \"jpeg\", \"png\", \"gif\"];\n\n    GraphicsImage.prototype.onAdded = function() {\n        if (this.properties[\"url\"] != \"\" && this.img == null) {\n            this.loadImage(this.properties[\"url\"]);\n        }\n    };\n\n    GraphicsImage.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n        if (this.img && this.size[0] > 5 && this.size[1] > 5 && this.img.width) {\n            ctx.drawImage(this.img, 0, 0, this.size[0], this.size[1]);\n        }\n    };\n\n    GraphicsImage.prototype.onExecute = function() {\n        if (!this.img) {\n            this.boxcolor = \"#000\";\n        }\n        if (this.img && this.img.width) {\n            this.setOutputData(0, this.img);\n        } else {\n            this.setOutputData(0, null);\n        }\n        if (this.img && this.img.dirty) {\n            this.img.dirty = false;\n        }\n    };\n\n    GraphicsImage.prototype.onPropertyChanged = function(name, value) {\n        this.properties[name] = value;\n        if (name == \"url\" && value != \"\") {\n            this.loadImage(value);\n        }\n\n        return true;\n    };\n\n    GraphicsImage.prototype.loadImage = function(url, callback) {\n        if (url == \"\") {\n            this.img = null;\n            return;\n        }\n\n        this.img = document.createElement(\"img\");\n\n        if (url.substr(0, 4) == \"http\" && LiteGraph.proxy) {\n            url = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\n        }\n\n        this.img.src = url;\n        this.boxcolor = \"#F95\";\n        var that = this;\n        this.img.onload = function() {\n            if (callback) {\n                callback(this);\n            }\n            console.log( \"Image loaded, size: \" + that.img.width + \"x\" + that.img.height );\n            this.dirty = true;\n            that.boxcolor = \"#9F9\";\n            that.setDirtyCanvas(true);\n        };\n        this.img.onerror = function() {\n\t\t\tconsole.log(\"error loading the image:\" + url);\n\t\t}\n    };\n\n    GraphicsImage.prototype.onWidget = function(e, widget) {\n        if (widget.name == \"load\") {\n            this.loadImage(this.properties[\"url\"]);\n        }\n    };\n\n    GraphicsImage.prototype.onDropFile = function(file) {\n        var that = this;\n        if (this._url) {\n            URL.revokeObjectURL(this._url);\n        }\n        this._url = URL.createObjectURL(file);\n        this.properties.url = this._url;\n        this.loadImage(this._url, function(img) {\n            that.size[1] = (img.height / img.width) * that.size[0];\n        });\n    };\n\n    LiteGraph.registerNodeType(\"graphics/image\", GraphicsImage);\n\n    function ColorPalette() {\n        this.addInput(\"f\", \"number\");\n        this.addOutput(\"Color\", \"color\");\n        this.properties = {\n            colorA: \"#444444\",\n            colorB: \"#44AAFF\",\n            colorC: \"#44FFAA\",\n            colorD: \"#FFFFFF\"\n        };\n    }\n\n    ColorPalette.title = \"Palette\";\n    ColorPalette.desc = \"Generates a color\";\n\n    ColorPalette.prototype.onExecute = function() {\n        var c = [];\n\n        if (this.properties.colorA != null) {\n            c.push(hex2num(this.properties.colorA));\n        }\n        if (this.properties.colorB != null) {\n            c.push(hex2num(this.properties.colorB));\n        }\n        if (this.properties.colorC != null) {\n            c.push(hex2num(this.properties.colorC));\n        }\n        if (this.properties.colorD != null) {\n            c.push(hex2num(this.properties.colorD));\n        }\n\n        var f = this.getInputData(0);\n        if (f == null) {\n            f = 0.5;\n        }\n        if (f > 1.0) {\n            f = 1.0;\n        } else if (f < 0.0) {\n            f = 0.0;\n        }\n\n        if (c.length == 0) {\n            return;\n        }\n\n        var result = [0, 0, 0];\n        if (f == 0) {\n            result = c[0];\n        } else if (f == 1) {\n            result = c[c.length - 1];\n        } else {\n            var pos = (c.length - 1) * f;\n            var c1 = c[Math.floor(pos)];\n            var c2 = c[Math.floor(pos) + 1];\n            var t = pos - Math.floor(pos);\n            result[0] = c1[0] * (1 - t) + c2[0] * t;\n            result[1] = c1[1] * (1 - t) + c2[1] * t;\n            result[2] = c1[2] * (1 - t) + c2[2] * t;\n        }\n\n        /*\n\tc[0] = 1.0 - Math.abs( Math.sin( 0.1 * reModular.getTime() * Math.PI) );\n\tc[1] = Math.abs( Math.sin( 0.07 * reModular.getTime() * Math.PI) );\n\tc[2] = Math.abs( Math.sin( 0.01 * reModular.getTime() * Math.PI) );\n\t*/\n\n        for (var i=0; i < result.length; i++) {\n            result[i] /= 255;\n        }\n\n        this.boxcolor = colorToString(result);\n        this.setOutputData(0, result);\n    };\n\n    LiteGraph.registerNodeType(\"color/palette\", ColorPalette);\n\n    function ImageFrame() {\n        this.addInput(\"\", \"image,canvas\");\n        this.size = [200, 200];\n    }\n\n    ImageFrame.title = \"Frame\";\n    ImageFrame.desc = \"Frame viewerew\";\n    ImageFrame.widgets = [\n        { name: \"resize\", text: \"Resize box\", type: \"button\" },\n        { name: \"view\", text: \"View Image\", type: \"button\" }\n    ];\n\n    ImageFrame.prototype.onDrawBackground = function(ctx) {\n        if (this.frame && !this.flags.collapsed) {\n            ctx.drawImage(this.frame, 0, 0, this.size[0], this.size[1]);\n        }\n    };\n\n    ImageFrame.prototype.onExecute = function() {\n        this.frame = this.getInputData(0);\n        this.setDirtyCanvas(true);\n    };\n\n    ImageFrame.prototype.onWidget = function(e, widget) {\n        if (widget.name == \"resize\" && this.frame) {\n            var width = this.frame.width;\n            var height = this.frame.height;\n\n            if (!width && this.frame.videoWidth != null) {\n                width = this.frame.videoWidth;\n                height = this.frame.videoHeight;\n            }\n\n            if (width && height) {\n                this.size = [width, height];\n            }\n            this.setDirtyCanvas(true, true);\n        } else if (widget.name == \"view\") {\n            this.show();\n        }\n    };\n\n    ImageFrame.prototype.show = function() {\n        //var str = this.canvas.toDataURL(\"image/png\");\n        if (showElement && this.frame) {\n            showElement(this.frame);\n        }\n    };\n\n    LiteGraph.registerNodeType(\"graphics/frame\", ImageFrame);\n\n    function ImageFade() {\n        this.addInputs([\n            [\"img1\", \"image\"],\n            [\"img2\", \"image\"],\n            [\"fade\", \"number\"]\n        ]);\n        this.addOutput(\"\", \"image\");\n        this.properties = { fade: 0.5, width: 512, height: 512 };\n    }\n\n    ImageFade.title = \"Image fade\";\n    ImageFade.desc = \"Fades between images\";\n    ImageFade.widgets = [\n        { name: \"resizeA\", text: \"Resize to A\", type: \"button\" },\n        { name: \"resizeB\", text: \"Resize to B\", type: \"button\" }\n    ];\n\n    ImageFade.prototype.onAdded = function() {\n        this.createCanvas();\n        var ctx = this.canvas.getContext(\"2d\");\n        ctx.fillStyle = \"#000\";\n        ctx.fillRect(0, 0, this.properties[\"width\"], this.properties[\"height\"]);\n    };\n\n    ImageFade.prototype.createCanvas = function() {\n        this.canvas = document.createElement(\"canvas\");\n        this.canvas.width = this.properties[\"width\"];\n        this.canvas.height = this.properties[\"height\"];\n    };\n\n    ImageFade.prototype.onExecute = function() {\n        var ctx = this.canvas.getContext(\"2d\");\n        this.canvas.width = this.canvas.width;\n\n        var A = this.getInputData(0);\n        if (A != null) {\n            ctx.drawImage(A, 0, 0, this.canvas.width, this.canvas.height);\n        }\n\n        var fade = this.getInputData(2);\n        if (fade == null) {\n            fade = this.properties[\"fade\"];\n        } else {\n            this.properties[\"fade\"] = fade;\n        }\n\n        ctx.globalAlpha = fade;\n        var B = this.getInputData(1);\n        if (B != null) {\n            ctx.drawImage(B, 0, 0, this.canvas.width, this.canvas.height);\n        }\n        ctx.globalAlpha = 1.0;\n\n        this.setOutputData(0, this.canvas);\n        this.setDirtyCanvas(true);\n    };\n\n    LiteGraph.registerNodeType(\"graphics/imagefade\", ImageFade);\n\n    function ImageCrop() {\n        this.addInput(\"\", \"image\");\n        this.addOutput(\"\", \"image\");\n        this.properties = { width: 256, height: 256, x: 0, y: 0, scale: 1.0 };\n        this.size = [50, 20];\n    }\n\n    ImageCrop.title = \"Crop\";\n    ImageCrop.desc = \"Crop Image\";\n\n    ImageCrop.prototype.onAdded = function() {\n        this.createCanvas();\n    };\n\n    ImageCrop.prototype.createCanvas = function() {\n        this.canvas = document.createElement(\"canvas\");\n        this.canvas.width = this.properties[\"width\"];\n        this.canvas.height = this.properties[\"height\"];\n    };\n\n    ImageCrop.prototype.onExecute = function() {\n        var input = this.getInputData(0);\n        if (!input) {\n            return;\n        }\n\n        if (input.width) {\n            var ctx = this.canvas.getContext(\"2d\");\n\n            ctx.drawImage(\n                input,\n                -this.properties[\"x\"],\n                -this.properties[\"y\"],\n                input.width * this.properties[\"scale\"],\n                input.height * this.properties[\"scale\"]\n            );\n            this.setOutputData(0, this.canvas);\n        } else {\n            this.setOutputData(0, null);\n        }\n    };\n\n    ImageCrop.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n        if (this.canvas) {\n            ctx.drawImage(\n                this.canvas,\n                0,\n                0,\n                this.canvas.width,\n                this.canvas.height,\n                0,\n                0,\n                this.size[0],\n                this.size[1]\n            );\n        }\n    };\n\n    ImageCrop.prototype.onPropertyChanged = function(name, value) {\n        this.properties[name] = value;\n\n        if (name == \"scale\") {\n            this.properties[name] = parseFloat(value);\n            if (this.properties[name] == 0) {\n                console.error(\"Error in scale\");\n                this.properties[name] = 1.0;\n            }\n        } else {\n            this.properties[name] = parseInt(value);\n        }\n\n        this.createCanvas();\n\n        return true;\n    };\n\n    LiteGraph.registerNodeType(\"graphics/cropImage\", ImageCrop);\n\n    //CANVAS stuff\n\n    function CanvasNode() {\n        this.addInput(\"clear\", LiteGraph.ACTION);\n        this.addOutput(\"\", \"canvas\");\n        this.properties = { width: 512, height: 512, autoclear: true };\n\n        this.canvas = document.createElement(\"canvas\");\n        this.ctx = this.canvas.getContext(\"2d\");\n    }\n\n    CanvasNode.title = \"Canvas\";\n    CanvasNode.desc = \"Canvas to render stuff\";\n\n    CanvasNode.prototype.onExecute = function() {\n        var canvas = this.canvas;\n        var w = this.properties.width | 0;\n        var h = this.properties.height | 0;\n        if (canvas.width != w) {\n            canvas.width = w;\n        }\n        if (canvas.height != h) {\n            canvas.height = h;\n        }\n\n        if (this.properties.autoclear) {\n            this.ctx.clearRect(0, 0, canvas.width, canvas.height);\n        }\n        this.setOutputData(0, canvas);\n    };\n\n    CanvasNode.prototype.onAction = function(action, param) {\n        if (action == \"clear\") {\n            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n        }\n    };\n\n    LiteGraph.registerNodeType(\"graphics/canvas\", CanvasNode);\n\n    function DrawImageNode() {\n        this.addInput(\"canvas\", \"canvas\");\n        this.addInput(\"img\", \"image,canvas\");\n        this.addInput(\"x\", \"number\");\n        this.addInput(\"y\", \"number\");\n        this.properties = { x: 0, y: 0, opacity: 1 };\n    }\n\n    DrawImageNode.title = \"DrawImage\";\n    DrawImageNode.desc = \"Draws image into a canvas\";\n\n    DrawImageNode.prototype.onExecute = function() {\n        var canvas = this.getInputData(0);\n        if (!canvas) {\n            return;\n        }\n\n        var img = this.getInputOrProperty(\"img\");\n        if (!img) {\n            return;\n        }\n\n        var x = this.getInputOrProperty(\"x\");\n        var y = this.getInputOrProperty(\"y\");\n        var ctx = canvas.getContext(\"2d\");\n        ctx.drawImage(img, x, y);\n    };\n\n    LiteGraph.registerNodeType(\"graphics/drawImage\", DrawImageNode);\n\n    function DrawRectangleNode() {\n        this.addInput(\"canvas\", \"canvas\");\n        this.addInput(\"x\", \"number\");\n        this.addInput(\"y\", \"number\");\n        this.addInput(\"w\", \"number\");\n        this.addInput(\"h\", \"number\");\n        this.properties = {\n            x: 0,\n            y: 0,\n            w: 10,\n            h: 10,\n            color: \"white\",\n            opacity: 1\n        };\n    }\n\n    DrawRectangleNode.title = \"DrawRectangle\";\n    DrawRectangleNode.desc = \"Draws rectangle in canvas\";\n\n    DrawRectangleNode.prototype.onExecute = function() {\n        var canvas = this.getInputData(0);\n        if (!canvas) {\n            return;\n        }\n\n        var x = this.getInputOrProperty(\"x\");\n        var y = this.getInputOrProperty(\"y\");\n        var w = this.getInputOrProperty(\"w\");\n        var h = this.getInputOrProperty(\"h\");\n        var ctx = canvas.getContext(\"2d\");\n        ctx.fillRect(x, y, w, h);\n    };\n\n    LiteGraph.registerNodeType(\"graphics/drawRectangle\", DrawRectangleNode);\n\n    function ImageVideo() {\n        this.addInput(\"t\", \"number\");\n        this.addOutputs([[\"frame\", \"image\"], [\"t\", \"number\"], [\"d\", \"number\"]]);\n        this.properties = { url: \"\", use_proxy: true };\n    }\n\n    ImageVideo.title = \"Video\";\n    ImageVideo.desc = \"Video playback\";\n    ImageVideo.widgets = [\n        { name: \"play\", text: \"PLAY\", type: \"minibutton\" },\n        { name: \"stop\", text: \"STOP\", type: \"minibutton\" },\n        { name: \"demo\", text: \"Demo video\", type: \"button\" },\n        { name: \"mute\", text: \"Mute video\", type: \"button\" }\n    ];\n\n    ImageVideo.prototype.onExecute = function() {\n        if (!this.properties.url) {\n            return;\n        }\n\n        if (this.properties.url != this._video_url) {\n            this.loadVideo(this.properties.url);\n        }\n\n        if (!this._video || this._video.width == 0) {\n            return;\n        }\n\n        var t = this.getInputData(0);\n        if (t && t >= 0 && t <= 1.0) {\n            this._video.currentTime = t * this._video.duration;\n            this._video.pause();\n        }\n\n        this._video.dirty = true;\n        this.setOutputData(0, this._video);\n        this.setOutputData(1, this._video.currentTime);\n        this.setOutputData(2, this._video.duration);\n        this.setDirtyCanvas(true);\n    };\n\n    ImageVideo.prototype.onStart = function() {\n        this.play();\n    };\n\n    ImageVideo.prototype.onStop = function() {\n        this.stop();\n    };\n\n    ImageVideo.prototype.loadVideo = function(url) {\n        this._video_url = url;\n\n\t\tvar pos = url.substr(0,10).indexOf(\":\");\n\t\tvar protocol = \"\";\n\t\tif(pos != -1)\n\t\t\tprotocol = url.substr(0,pos);\n\n\t\tvar host = \"\";\n\t\tif(protocol)\n\t\t{\n\t\t\thost = url.substr(0,url.indexOf(\"/\",protocol.length + 3));\n\t\t\thost = host.substr(protocol.length+3);\n\t\t}\n\n        if (\n            this.properties.use_proxy &&\n            protocol &&\n            LiteGraph.proxy &&\n\t\t\thost != location.host\n        ) {\n            url = LiteGraph.proxy + url.substr(url.indexOf(\":\") + 3);\n        }\n\n        this._video = document.createElement(\"video\");\n        this._video.src = url;\n        this._video.type = \"type=video/mp4\";\n\n        this._video.muted = true;\n        this._video.autoplay = true;\n\n        var that = this;\n        this._video.addEventListener(\"loadedmetadata\", function(e) {\n            //onload\n            console.log(\"Duration: \" + this.duration + \" seconds\");\n            console.log(\"Size: \" + this.videoWidth + \",\" + this.videoHeight);\n            that.setDirtyCanvas(true);\n            this.width = this.videoWidth;\n            this.height = this.videoHeight;\n        });\n        this._video.addEventListener(\"progress\", function(e) {\n            //onload\n            console.log(\"video loading...\");\n        });\n        this._video.addEventListener(\"error\", function(e) {\n            console.error(\"Error loading video: \" + this.src);\n            if (this.error) {\n                switch (this.error.code) {\n                    case this.error.MEDIA_ERR_ABORTED:\n                        console.error(\"You stopped the video.\");\n                        break;\n                    case this.error.MEDIA_ERR_NETWORK:\n                        console.error(\"Network error - please try again later.\");\n                        break;\n                    case this.error.MEDIA_ERR_DECODE:\n                        console.error(\"Video is broken..\");\n                        break;\n                    case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:\n                        console.error(\"Sorry, your browser can't play this video.\");\n                        break;\n                }\n            }\n        });\n\n        this._video.addEventListener(\"ended\", function(e) {\n            console.log(\"Video Ended.\");\n            this.play(); //loop\n        });\n\n        //document.body.appendChild(this.video);\n    };\n\n    ImageVideo.prototype.onPropertyChanged = function(name, value) {\n        this.properties[name] = value;\n        if (name == \"url\" && value != \"\") {\n            this.loadVideo(value);\n        }\n\n        return true;\n    };\n\n    ImageVideo.prototype.play = function() {\n        if (this._video && this._video.videoWidth ) { //is loaded\n            this._video.play();\n        }\n    };\n\n    ImageVideo.prototype.playPause = function() {\n        if (!this._video) {\n            return;\n        }\n        if (this._video.paused) {\n            this.play();\n        } else {\n            this.pause();\n        }\n    };\n\n    ImageVideo.prototype.stop = function() {\n        if (!this._video) {\n            return;\n        }\n        this._video.pause();\n        this._video.currentTime = 0;\n    };\n\n    ImageVideo.prototype.pause = function() {\n        if (!this._video) {\n            return;\n        }\n        console.log(\"Video paused\");\n        this._video.pause();\n    };\n\n    ImageVideo.prototype.onWidget = function(e, widget) {\n        /*\n\tif(widget.name == \"demo\")\n\t{\n\t\tthis.loadVideo();\n\t}\n\telse if(widget.name == \"play\")\n\t{\n\t\tif(this._video)\n\t\t\tthis.playPause();\n\t}\n\tif(widget.name == \"stop\")\n\t{\n\t\tthis.stop();\n\t}\n\telse if(widget.name == \"mute\")\n\t{\n\t\tif(this._video)\n\t\t\tthis._video.muted = !this._video.muted;\n\t}\n\t*/\n    };\n\n    LiteGraph.registerNodeType(\"graphics/video\", ImageVideo);\n\n    // Texture Webcam *****************************************\n    function ImageWebcam() {\n        this.addOutput(\"Webcam\", \"image\");\n        this.properties = { filterFacingMode: false, facingMode: \"user\" };\n        this.boxcolor = \"black\";\n        this.frame = 0;\n    }\n\n    ImageWebcam.title = \"Webcam\";\n    ImageWebcam.desc = \"Webcam image\";\n    ImageWebcam.is_webcam_open = false;\n\n    ImageWebcam.prototype.openStream = function() {\n        if (!navigator.mediaDevices.getUserMedia) {\n            console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags');\n            return;\n        }\n\n        this._waiting_confirmation = true;\n\n        // Not showing vendor prefixes.\n        var constraints = {\n            audio: false,\n            video: !this.properties.filterFacingMode ? true : { facingMode: this.properties.facingMode }\n        };\n        navigator.mediaDevices\n            .getUserMedia(constraints)\n            .then(this.streamReady.bind(this))\n            .catch(onFailSoHard);\n\n        var that = this;\n        function onFailSoHard(e) {\n            console.log(\"Webcam rejected\", e);\n            that._webcam_stream = false;\n            ImageWebcam.is_webcam_open = false;\n            that.boxcolor = \"red\";\n            that.trigger(\"stream_error\");\n        }\n    };\n\n    ImageWebcam.prototype.closeStream = function() {\n        if (this._webcam_stream) {\n            var tracks = this._webcam_stream.getTracks();\n            if (tracks.length) {\n                for (var i = 0; i < tracks.length; ++i) {\n                    tracks[i].stop();\n                }\n            }\n            ImageWebcam.is_webcam_open = false;\n            this._webcam_stream = null;\n            this._video = null;\n            this.boxcolor = \"black\";\n            this.trigger(\"stream_closed\");\n        }\n    };\n\n    ImageWebcam.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"facingMode\") {\n            this.properties.facingMode = value;\n            this.closeStream();\n            this.openStream();\n        }\n    };\n\n    ImageWebcam.prototype.onRemoved = function() {\n        this.closeStream();\n    };\n\n    ImageWebcam.prototype.streamReady = function(localMediaStream) {\n        this._webcam_stream = localMediaStream;\n        //this._waiting_confirmation = false;\n        this.boxcolor = \"green\";\n\n        var video = this._video;\n        if (!video) {\n            video = document.createElement(\"video\");\n            video.autoplay = true;\n            video.srcObject = localMediaStream;\n            this._video = video;\n            //document.body.appendChild( video ); //debug\n            //when video info is loaded (size and so)\n            video.onloadedmetadata = function(e) {\n                // Ready to go. Do some stuff.\n                console.log(e);\n                ImageWebcam.is_webcam_open = true;\n            };\n        }\n\n        this.trigger(\"stream_ready\", video);\n    };\n\n    ImageWebcam.prototype.onExecute = function() {\n        if (this._webcam_stream == null && !this._waiting_confirmation) {\n            this.openStream();\n        }\n\n        if (!this._video || !this._video.videoWidth) {\n            return;\n        }\n\n        this._video.frame = ++this.frame;\n        this._video.width = this._video.videoWidth;\n        this._video.height = this._video.videoHeight;\n        this.setOutputData(0, this._video);\n        for (var i = 1; i < this.outputs.length; ++i) {\n            if (!this.outputs[i]) {\n                continue;\n            }\n            switch (this.outputs[i].name) {\n                case \"width\":\n                    this.setOutputData(i, this._video.videoWidth);\n                    break;\n                case \"height\":\n                    this.setOutputData(i, this._video.videoHeight);\n                    break;\n            }\n        }\n    };\n\n    ImageWebcam.prototype.getExtraMenuOptions = function(graphcanvas) {\n        var that = this;\n        var txt = !that.properties.show ? \"Show Frame\" : \"Hide Frame\";\n        return [\n            {\n                content: txt,\n                callback: function() {\n                    that.properties.show = !that.properties.show;\n                }\n            }\n        ];\n    };\n\n    ImageWebcam.prototype.onDrawBackground = function(ctx) {\n        if (\n            this.flags.collapsed ||\n            this.size[1] <= 20 ||\n            !this.properties.show\n        ) {\n            return;\n        }\n\n        if (!this._video) {\n            return;\n        }\n\n        //render to graph canvas\n        ctx.save();\n        ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]);\n        ctx.restore();\n    };\n\n    ImageWebcam.prototype.onGetOutputs = function() {\n        return [\n            [\"width\", \"number\"],\n            [\"height\", \"number\"],\n            [\"stream_ready\", LiteGraph.EVENT],\n            [\"stream_closed\", LiteGraph.EVENT],\n            [\"stream_error\", LiteGraph.EVENT]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"graphics/webcam\", ImageWebcam);\n})(this);\n"
  },
  {
    "path": "src/nodes/input.js",
    "content": "(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    function GamepadInput() {\r\n        this.addOutput(\"left_x_axis\", \"number\");\r\n        this.addOutput(\"left_y_axis\", \"number\");\r\n        this.addOutput(\"button_pressed\", LiteGraph.EVENT);\r\n        this.properties = { gamepad_index: 0, threshold: 0.1 };\r\n\r\n        this._left_axis = new Float32Array(2);\r\n        this._right_axis = new Float32Array(2);\r\n        this._triggers = new Float32Array(2);\r\n        this._previous_buttons = new Uint8Array(17);\r\n        this._current_buttons = new Uint8Array(17);\r\n    }\r\n\r\n    GamepadInput.title = \"Gamepad\";\r\n    GamepadInput.desc = \"gets the input of the gamepad\";\r\n\r\n    GamepadInput.CENTER = 0;\r\n    GamepadInput.LEFT = 1;\r\n    GamepadInput.RIGHT = 2;\r\n    GamepadInput.UP = 4;\r\n    GamepadInput.DOWN = 8;\r\n\r\n    GamepadInput.zero = new Float32Array(2);\r\n    GamepadInput.buttons = [\r\n        \"a\",\r\n        \"b\",\r\n        \"x\",\r\n        \"y\",\r\n        \"lb\",\r\n        \"rb\",\r\n        \"lt\",\r\n        \"rt\",\r\n        \"back\",\r\n        \"start\",\r\n        \"ls\",\r\n        \"rs\",\r\n        \"home\"\r\n    ];\r\n\r\n    GamepadInput.prototype.onExecute = function() {\r\n        //get gamepad\r\n        var gamepad = this.getGamepad();\r\n        var threshold = this.properties.threshold || 0.0;\r\n\r\n        if (gamepad) {\r\n            this._left_axis[0] =\r\n                Math.abs(gamepad.xbox.axes[\"lx\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"lx\"]\r\n                    : 0;\r\n            this._left_axis[1] =\r\n                Math.abs(gamepad.xbox.axes[\"ly\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"ly\"]\r\n                    : 0;\r\n            this._right_axis[0] =\r\n                Math.abs(gamepad.xbox.axes[\"rx\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"rx\"]\r\n                    : 0;\r\n            this._right_axis[1] =\r\n                Math.abs(gamepad.xbox.axes[\"ry\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"ry\"]\r\n                    : 0;\r\n            this._triggers[0] =\r\n                Math.abs(gamepad.xbox.axes[\"ltrigger\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"ltrigger\"]\r\n                    : 0;\r\n            this._triggers[1] =\r\n                Math.abs(gamepad.xbox.axes[\"rtrigger\"]) > threshold\r\n                    ? gamepad.xbox.axes[\"rtrigger\"]\r\n                    : 0;\r\n        }\r\n\r\n        if (this.outputs) {\r\n            for (var i = 0; i < this.outputs.length; i++) {\r\n                var output = this.outputs[i];\r\n                if (!output.links || !output.links.length) {\r\n                    continue;\r\n                }\r\n                var v = null;\r\n\r\n                if (gamepad) {\r\n                    switch (output.name) {\r\n                        case \"left_axis\":\r\n                            v = this._left_axis;\r\n                            break;\r\n                        case \"right_axis\":\r\n                            v = this._right_axis;\r\n                            break;\r\n                        case \"left_x_axis\":\r\n                            v = this._left_axis[0];\r\n                            break;\r\n                        case \"left_y_axis\":\r\n                            v = this._left_axis[1];\r\n                            break;\r\n                        case \"right_x_axis\":\r\n                            v = this._right_axis[0];\r\n                            break;\r\n                        case \"right_y_axis\":\r\n                            v = this._right_axis[1];\r\n                            break;\r\n                        case \"trigger_left\":\r\n                            v = this._triggers[0];\r\n                            break;\r\n                        case \"trigger_right\":\r\n                            v = this._triggers[1];\r\n                            break;\r\n                        case \"a_button\":\r\n                            v = gamepad.xbox.buttons[\"a\"] ? 1 : 0;\r\n                            break;\r\n                        case \"b_button\":\r\n                            v = gamepad.xbox.buttons[\"b\"] ? 1 : 0;\r\n                            break;\r\n                        case \"x_button\":\r\n                            v = gamepad.xbox.buttons[\"x\"] ? 1 : 0;\r\n                            break;\r\n                        case \"y_button\":\r\n                            v = gamepad.xbox.buttons[\"y\"] ? 1 : 0;\r\n                            break;\r\n                        case \"lb_button\":\r\n                            v = gamepad.xbox.buttons[\"lb\"] ? 1 : 0;\r\n                            break;\r\n                        case \"rb_button\":\r\n                            v = gamepad.xbox.buttons[\"rb\"] ? 1 : 0;\r\n                            break;\r\n                        case \"ls_button\":\r\n                            v = gamepad.xbox.buttons[\"ls\"] ? 1 : 0;\r\n                            break;\r\n                        case \"rs_button\":\r\n                            v = gamepad.xbox.buttons[\"rs\"] ? 1 : 0;\r\n                            break;\r\n                        case \"hat_left\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.LEFT;\r\n                            break;\r\n                        case \"hat_right\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.RIGHT;\r\n                            break;\r\n                        case \"hat_up\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.UP;\r\n                            break;\r\n                        case \"hat_down\":\r\n                            v = gamepad.xbox.hatmap & GamepadInput.DOWN;\r\n                            break;\r\n                        case \"hat\":\r\n                            v = gamepad.xbox.hatmap;\r\n                            break;\r\n                        case \"start_button\":\r\n                            v = gamepad.xbox.buttons[\"start\"] ? 1 : 0;\r\n                            break;\r\n                        case \"back_button\":\r\n                            v = gamepad.xbox.buttons[\"back\"] ? 1 : 0;\r\n                            break;\r\n                        case \"button_pressed\":\r\n                            for (\r\n                                var j = 0;\r\n                                j < this._current_buttons.length;\r\n                                ++j\r\n                            ) {\r\n                                if (\r\n                                    this._current_buttons[j] &&\r\n                                    !this._previous_buttons[j]\r\n                                ) {\r\n                                    this.triggerSlot(\r\n                                        i,\r\n                                        GamepadInput.buttons[j]\r\n                                    );\r\n                                }\r\n                            }\r\n                            break;\r\n                        default:\r\n                            break;\r\n                    }\r\n                } else {\r\n                    //if no gamepad is connected, output 0\r\n                    switch (output.name) {\r\n                        case \"button_pressed\":\r\n                            break;\r\n                        case \"left_axis\":\r\n                        case \"right_axis\":\r\n                            v = GamepadInput.zero;\r\n                            break;\r\n                        default:\r\n                            v = 0;\r\n                    }\r\n                }\r\n                this.setOutputData(i, v);\r\n            }\r\n        }\r\n    };\r\n\r\n\tGamepadInput.mapping = {a:0,b:1,x:2,y:3,lb:4,rb:5,lt:6,rt:7,back:8,start:9,ls:10,rs:11 };\r\n\tGamepadInput.mapping_array = [\"a\",\"b\",\"x\",\"y\",\"lb\",\"rb\",\"lt\",\"rt\",\"back\",\"start\",\"ls\",\"rs\"];\r\n\r\n    GamepadInput.prototype.getGamepad = function() {\r\n        var getGamepads =\r\n            navigator.getGamepads ||\r\n            navigator.webkitGetGamepads ||\r\n            navigator.mozGetGamepads;\r\n        if (!getGamepads) {\r\n            return null;\r\n        }\r\n        var gamepads = getGamepads.call(navigator);\r\n        var gamepad = null;\r\n\r\n        this._previous_buttons.set(this._current_buttons);\r\n\r\n        //pick the first connected\r\n        for (var i = this.properties.gamepad_index; i < 4; i++) {\r\n            if (!gamepads[i]) {\r\n                continue;\r\n            }\r\n            gamepad = gamepads[i];\r\n\r\n            //xbox controller mapping\r\n            var xbox = this.xbox_mapping;\r\n            if (!xbox) {\r\n                xbox = this.xbox_mapping = {\r\n                    axes: [],\r\n                    buttons: {},\r\n                    hat: \"\",\r\n                    hatmap: GamepadInput.CENTER\r\n                };\r\n            }\r\n\r\n            xbox.axes[\"lx\"] = gamepad.axes[0];\r\n            xbox.axes[\"ly\"] = gamepad.axes[1];\r\n            xbox.axes[\"rx\"] = gamepad.axes[2];\r\n            xbox.axes[\"ry\"] = gamepad.axes[3];\r\n            xbox.axes[\"ltrigger\"] = gamepad.buttons[6].value;\r\n            xbox.axes[\"rtrigger\"] = gamepad.buttons[7].value;\r\n            xbox.hat = \"\";\r\n            xbox.hatmap = GamepadInput.CENTER;\r\n\r\n            for (var j = 0; j < gamepad.buttons.length; j++) {\r\n                this._current_buttons[j] = gamepad.buttons[j].pressed;\r\n\r\n\t\t\t\tif(j < 12)\r\n\t\t\t\t{\r\n\t\t\t\t\txbox.buttons[ GamepadInput.mapping_array[j] ] = gamepad.buttons[j].pressed;\r\n\t\t\t\t\tif(gamepad.buttons[j].was_pressed)\r\n\t\t\t\t\t\tthis.trigger( GamepadInput.mapping_array[j] + \"_button_event\" );\r\n\t\t\t\t}\r\n\t\t\t\telse //mapping of XBOX\r\n\t\t\t\t\tswitch ( j ) //I use a switch to ensure that a player with another gamepad could play\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcase 12:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"up\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.UP;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 13:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"down\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.DOWN;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 14:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"left\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.LEFT;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 15:\r\n\t\t\t\t\t\t\tif (gamepad.buttons[j].pressed) {\r\n\t\t\t\t\t\t\t\txbox.hat += \"right\";\r\n\t\t\t\t\t\t\t\txbox.hatmap |= GamepadInput.RIGHT;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tcase 16:\r\n\t\t\t\t\t\t\txbox.buttons[\"home\"] = gamepad.buttons[j].pressed;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\tdefault:\r\n\t\t\t\t\t}\r\n            }\r\n            gamepad.xbox = xbox;\r\n            return gamepad;\r\n        }\r\n    };\r\n\r\n    GamepadInput.prototype.onDrawBackground = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        //render gamepad state?\r\n        var la = this._left_axis;\r\n        var ra = this._right_axis;\r\n        ctx.strokeStyle = \"#88A\";\r\n        ctx.strokeRect(\r\n            (la[0] + 1) * 0.5 * this.size[0] - 4,\r\n            (la[1] + 1) * 0.5 * this.size[1] - 4,\r\n            8,\r\n            8\r\n        );\r\n        ctx.strokeStyle = \"#8A8\";\r\n        ctx.strokeRect(\r\n            (ra[0] + 1) * 0.5 * this.size[0] - 4,\r\n            (ra[1] + 1) * 0.5 * this.size[1] - 4,\r\n            8,\r\n            8\r\n        );\r\n        var h = this.size[1] / this._current_buttons.length;\r\n        ctx.fillStyle = \"#AEB\";\r\n        for (var i = 0; i < this._current_buttons.length; ++i) {\r\n            if (this._current_buttons[i]) {\r\n                ctx.fillRect(0, h * i, 6, h);\r\n            }\r\n        }\r\n    };\r\n\r\n    GamepadInput.prototype.onGetOutputs = function() {\r\n        return [\r\n            [\"left_axis\", \"vec2\"],\r\n            [\"right_axis\", \"vec2\"],\r\n            [\"left_x_axis\", \"number\"],\r\n            [\"left_y_axis\", \"number\"],\r\n            [\"right_x_axis\", \"number\"],\r\n            [\"right_y_axis\", \"number\"],\r\n            [\"trigger_left\", \"number\"],\r\n            [\"trigger_right\", \"number\"],\r\n            [\"a_button\", \"number\"],\r\n            [\"b_button\", \"number\"],\r\n            [\"x_button\", \"number\"],\r\n            [\"y_button\", \"number\"],\r\n            [\"lb_button\", \"number\"],\r\n            [\"rb_button\", \"number\"],\r\n            [\"ls_button\", \"number\"],\r\n            [\"rs_button\", \"number\"],\r\n            [\"start_button\", \"number\"],\r\n            [\"back_button\", \"number\"],\r\n            [\"a_button_event\", LiteGraph.EVENT ],\r\n            [\"b_button_event\", LiteGraph.EVENT ],\r\n            [\"x_button_event\", LiteGraph.EVENT ],\r\n            [\"y_button_event\", LiteGraph.EVENT ],\r\n            [\"lb_button_event\", LiteGraph.EVENT ],\r\n            [\"rb_button_event\", LiteGraph.EVENT ],\r\n            [\"ls_button_event\", LiteGraph.EVENT ],\r\n            [\"rs_button_event\", LiteGraph.EVENT ],\r\n            [\"start_button_event\", LiteGraph.EVENT ],\r\n            [\"back_button_event\", LiteGraph.EVENT ],\r\n            [\"hat_left\", \"number\"],\r\n            [\"hat_right\", \"number\"],\r\n            [\"hat_up\", \"number\"],\r\n            [\"hat_down\", \"number\"],\r\n            [\"hat\", \"number\"],\r\n            [\"button_pressed\", LiteGraph.EVENT]\r\n        ];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"input/gamepad\", GamepadInput);\r\n\r\n})(this);\r\n"
  },
  {
    "path": "src/nodes/interface.js",
    "content": "//widgets\n(function(global) {\n    var LiteGraph = global.LiteGraph;\n\n    /* Button ****************/\n\n    function WidgetButton() {\n        this.addOutput(\"\", LiteGraph.EVENT);\n        this.addOutput(\"\", \"boolean\");\n        this.addProperty(\"text\", \"click me\");\n        this.addProperty(\"font_size\", 30);\n        this.addProperty(\"message\", \"\");\n        this.size = [164, 84];\n        this.clicked = false;\n    }\n\n    WidgetButton.title = \"Button\";\n    WidgetButton.desc = \"Triggers an event\";\n\n    WidgetButton.font = \"Arial\";\n    WidgetButton.prototype.onDrawForeground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n        var margin = 10;\n        ctx.fillStyle = \"black\";\n        ctx.fillRect(\n            margin + 1,\n            margin + 1,\n            this.size[0] - margin * 2,\n            this.size[1] - margin * 2\n        );\n        ctx.fillStyle = \"#AAF\";\n        ctx.fillRect(\n            margin - 1,\n            margin - 1,\n            this.size[0] - margin * 2,\n            this.size[1] - margin * 2\n        );\n        ctx.fillStyle = this.clicked\n            ? \"white\"\n            : this.mouseOver\n            ? \"#668\"\n            : \"#334\";\n        ctx.fillRect(\n            margin,\n            margin,\n            this.size[0] - margin * 2,\n            this.size[1] - margin * 2\n        );\n\n        if (this.properties.text || this.properties.text === 0) {\n            var font_size = this.properties.font_size || 30;\n            ctx.textAlign = \"center\";\n            ctx.fillStyle = this.clicked ? \"black\" : \"white\";\n            ctx.font = font_size + \"px \" + WidgetButton.font;\n            ctx.fillText(\n                this.properties.text,\n                this.size[0] * 0.5,\n                this.size[1] * 0.5 + font_size * 0.3\n            );\n            ctx.textAlign = \"left\";\n        }\n    };\n\n    WidgetButton.prototype.onMouseDown = function(e, local_pos) {\n        if (\n            local_pos[0] > 1 &&\n            local_pos[1] > 1 &&\n            local_pos[0] < this.size[0] - 2 &&\n            local_pos[1] < this.size[1] - 2\n        ) {\n            this.clicked = true;\n            this.setOutputData(1, this.clicked);\n            this.triggerSlot(0, this.properties.message);\n            return true;\n        }\n    };\n\n    WidgetButton.prototype.onExecute = function() {\n        this.setOutputData(1, this.clicked);\n    };\n\n    WidgetButton.prototype.onMouseUp = function(e) {\n        this.clicked = false;\n    };\n\n    LiteGraph.registerNodeType(\"widget/button\", WidgetButton);\n\n    function WidgetToggle() {\n        this.addInput(\"\", \"boolean\");\n        this.addInput(\"e\", LiteGraph.ACTION);\n        this.addOutput(\"v\", \"boolean\");\n        this.addOutput(\"e\", LiteGraph.EVENT);\n        this.properties = { font: \"\", value: false };\n        this.size = [160, 44];\n    }\n\n    WidgetToggle.title = \"Toggle\";\n    WidgetToggle.desc = \"Toggles between true or false\";\n\n    WidgetToggle.prototype.onDrawForeground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        var size = this.size[1] * 0.5;\n        var margin = 0.25;\n        var h = this.size[1] * 0.8;\n        ctx.font = this.properties.font || (size * 0.8).toFixed(0) + \"px Arial\";\n        var w = ctx.measureText(this.title).width;\n        var x = (this.size[0] - (w + size)) * 0.5;\n\n        ctx.fillStyle = \"#AAA\";\n        ctx.fillRect(x, h - size, size, size);\n\n        ctx.fillStyle = this.properties.value ? \"#AEF\" : \"#000\";\n        ctx.fillRect(\n            x + size * margin,\n            h - size + size * margin,\n            size * (1 - margin * 2),\n            size * (1 - margin * 2)\n        );\n\n        ctx.textAlign = \"left\";\n        ctx.fillStyle = \"#AAA\";\n        ctx.fillText(this.title, size * 1.2 + x, h * 0.85);\n        ctx.textAlign = \"left\";\n    };\n\n    WidgetToggle.prototype.onAction = function(action) {\n        this.properties.value = !this.properties.value;\n        this.trigger(\"e\", this.properties.value);\n    };\n\n    WidgetToggle.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v != null) {\n            this.properties.value = v;\n        }\n        this.setOutputData(0, this.properties.value);\n    };\n\n    WidgetToggle.prototype.onMouseDown = function(e, local_pos) {\n        if (\n            local_pos[0] > 1 &&\n            local_pos[1] > 1 &&\n            local_pos[0] < this.size[0] - 2 &&\n            local_pos[1] < this.size[1] - 2\n        ) {\n            this.properties.value = !this.properties.value;\n            this.graph._version++;\n            this.trigger(\"e\", this.properties.value);\n            return true;\n        }\n    };\n\n    LiteGraph.registerNodeType(\"widget/toggle\", WidgetToggle);\n\n    /* Number ****************/\n\n    function WidgetNumber() {\n        this.addOutput(\"\", \"number\");\n        this.size = [80, 60];\n        this.properties = { min: -1000, max: 1000, value: 1, step: 1 };\n        this.old_y = -1;\n        this._remainder = 0;\n        this._precision = 0;\n        this.mouse_captured = false;\n    }\n\n    WidgetNumber.title = \"Number\";\n    WidgetNumber.desc = \"Widget to select number value\";\n\n    WidgetNumber.pixels_threshold = 10;\n    WidgetNumber.markers_color = \"#666\";\n\n    WidgetNumber.prototype.onDrawForeground = function(ctx) {\n        var x = this.size[0] * 0.5;\n        var h = this.size[1];\n        if (h > 30) {\n            ctx.fillStyle = WidgetNumber.markers_color;\n            ctx.beginPath();\n            ctx.moveTo(x, h * 0.1);\n            ctx.lineTo(x + h * 0.1, h * 0.2);\n            ctx.lineTo(x + h * -0.1, h * 0.2);\n            ctx.fill();\n            ctx.beginPath();\n            ctx.moveTo(x, h * 0.9);\n            ctx.lineTo(x + h * 0.1, h * 0.8);\n            ctx.lineTo(x + h * -0.1, h * 0.8);\n            ctx.fill();\n            ctx.font = (h * 0.7).toFixed(1) + \"px Arial\";\n        } else {\n            ctx.font = (h * 0.8).toFixed(1) + \"px Arial\";\n        }\n\n        ctx.textAlign = \"center\";\n        ctx.font = (h * 0.7).toFixed(1) + \"px Arial\";\n        ctx.fillStyle = \"#EEE\";\n        ctx.fillText(\n            this.properties.value.toFixed(this._precision),\n            x,\n            h * 0.75\n        );\n    };\n\n    WidgetNumber.prototype.onExecute = function() {\n        this.setOutputData(0, this.properties.value);\n    };\n\n    WidgetNumber.prototype.onPropertyChanged = function(name, value) {\n        var t = (this.properties.step + \"\").split(\".\");\n        this._precision = t.length > 1 ? t[1].length : 0;\n    };\n\n    WidgetNumber.prototype.onMouseDown = function(e, pos) {\n        if (pos[1] < 0) {\n            return;\n        }\n\n        this.old_y = e.canvasY;\n        this.captureInput(true);\n        this.mouse_captured = true;\n\n        return true;\n    };\n\n    WidgetNumber.prototype.onMouseMove = function(e) {\n        if (!this.mouse_captured) {\n            return;\n        }\n\n        var delta = this.old_y - e.canvasY;\n        if (e.shiftKey) {\n            delta *= 10;\n        }\n        if (e.metaKey || e.altKey) {\n            delta *= 0.1;\n        }\n        this.old_y = e.canvasY;\n\n        var steps = this._remainder + delta / WidgetNumber.pixels_threshold;\n        this._remainder = steps % 1;\n        steps = steps | 0;\n\n        var v = clamp(\n            this.properties.value + steps * this.properties.step,\n            this.properties.min,\n            this.properties.max\n        );\n        this.properties.value = v;\n        this.graph._version++;\n        this.setDirtyCanvas(true);\n    };\n\n    WidgetNumber.prototype.onMouseUp = function(e, pos) {\n        if (e.click_time < 200) {\n            var steps = pos[1] > this.size[1] * 0.5 ? -1 : 1;\n            this.properties.value = clamp(\n                this.properties.value + steps * this.properties.step,\n                this.properties.min,\n                this.properties.max\n            );\n            this.graph._version++;\n            this.setDirtyCanvas(true);\n        }\n\n        if (this.mouse_captured) {\n            this.mouse_captured = false;\n            this.captureInput(false);\n        }\n    };\n\n    LiteGraph.registerNodeType(\"widget/number\", WidgetNumber);\n\n\n    /* Combo ****************/\n\n    function WidgetCombo() {\n        this.addOutput(\"\", \"string\");\n        this.addOutput(\"change\", LiteGraph.EVENT);\n        this.size = [80, 60];\n        this.properties = { value: \"A\", values:\"A;B;C\" };\n        this.old_y = -1;\n        this.mouse_captured = false;\n\t\tthis._values = this.properties.values.split(\";\");\n\t\tvar that = this;\n        this.widgets_up = true;\n\t\tthis.widget = this.addWidget(\"combo\",\"\", this.properties.value, function(v){\n\t\t\tthat.properties.value = v;\n            that.triggerSlot(1, v);\n\t\t}, { property: \"value\", values: this._values } );\n    }\n\n    WidgetCombo.title = \"Combo\";\n    WidgetCombo.desc = \"Widget to select from a list\";\n\n    WidgetCombo.prototype.onExecute = function() {\n        this.setOutputData( 0, this.properties.value );\n    };\n\n    WidgetCombo.prototype.onPropertyChanged = function(name, value) {\n\t\tif(name == \"values\")\n\t\t{\n\t\t\tthis._values = value.split(\";\");\n\t\t\tthis.widget.options.values = this._values;\n\t\t}\n\t\telse if(name == \"value\")\n\t\t{\n\t\t\tthis.widget.value = value;\n\t\t}\n\t};\n\n    LiteGraph.registerNodeType(\"widget/combo\", WidgetCombo);\n\n\n    /* Knob ****************/\n\n    function WidgetKnob() {\n        this.addOutput(\"\", \"number\");\n        this.size = [64, 84];\n        this.properties = {\n            min: 0,\n            max: 1,\n            value: 0.5,\n            color: \"#7AF\",\n            precision: 2\n        };\n        this.value = -1;\n    }\n\n    WidgetKnob.title = \"Knob\";\n    WidgetKnob.desc = \"Circular controller\";\n    WidgetKnob.size = [80, 100];\n\n    WidgetKnob.prototype.onDrawForeground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        if (this.value == -1) {\n            this.value =\n                (this.properties.value - this.properties.min) /\n                (this.properties.max - this.properties.min);\n        }\n\n        var center_x = this.size[0] * 0.5;\n        var center_y = this.size[1] * 0.5;\n        var radius = Math.min(this.size[0], this.size[1]) * 0.5 - 5;\n        var w = Math.floor(radius * 0.05);\n\n        ctx.globalAlpha = 1;\n        ctx.save();\n        ctx.translate(center_x, center_y);\n        ctx.rotate(Math.PI * 0.75);\n\n        //bg\n        ctx.fillStyle = \"rgba(0,0,0,0.5)\";\n        ctx.beginPath();\n        ctx.moveTo(0, 0);\n        ctx.arc(0, 0, radius, 0, Math.PI * 1.5);\n        ctx.fill();\n\n        //value\n        ctx.strokeStyle = \"black\";\n        ctx.fillStyle = this.properties.color;\n        ctx.lineWidth = 2;\n        ctx.beginPath();\n        ctx.moveTo(0, 0);\n        ctx.arc(\n            0,\n            0,\n            radius - 4,\n            0,\n            Math.PI * 1.5 * Math.max(0.01, this.value)\n        );\n        ctx.closePath();\n        ctx.fill();\n        //ctx.stroke();\n        ctx.lineWidth = 1;\n        ctx.globalAlpha = 1;\n        ctx.restore();\n\n        //inner\n        ctx.fillStyle = \"black\";\n        ctx.beginPath();\n        ctx.arc(center_x, center_y, radius * 0.75, 0, Math.PI * 2, true);\n        ctx.fill();\n\n        //miniball\n        ctx.fillStyle = this.mouseOver ? \"white\" : this.properties.color;\n        ctx.beginPath();\n        var angle = this.value * Math.PI * 1.5 + Math.PI * 0.75;\n        ctx.arc(\n            center_x + Math.cos(angle) * radius * 0.65,\n            center_y + Math.sin(angle) * radius * 0.65,\n            radius * 0.05,\n            0,\n            Math.PI * 2,\n            true\n        );\n        ctx.fill();\n\n        //text\n        ctx.fillStyle = this.mouseOver ? \"white\" : \"#AAA\";\n        ctx.font = Math.floor(radius * 0.5) + \"px Arial\";\n        ctx.textAlign = \"center\";\n        ctx.fillText(\n            this.properties.value.toFixed(this.properties.precision),\n            center_x,\n            center_y + radius * 0.15\n        );\n    };\n\n    WidgetKnob.prototype.onExecute = function() {\n        this.setOutputData(0, this.properties.value);\n        this.boxcolor = LiteGraph.colorToString([\n            this.value,\n            this.value,\n            this.value\n        ]);\n    };\n\n    WidgetKnob.prototype.onMouseDown = function(e) {\n        this.center = [this.size[0] * 0.5, this.size[1] * 0.5 + 20];\n        this.radius = this.size[0] * 0.5;\n        if (\n            e.canvasY - this.pos[1] < 20 ||\n            LiteGraph.distance(\n                [e.canvasX, e.canvasY],\n                [this.pos[0] + this.center[0], this.pos[1] + this.center[1]]\n            ) > this.radius\n        ) {\n            return false;\n        }\n        this.oldmouse = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]];\n        this.captureInput(true);\n        return true;\n    };\n\n    WidgetKnob.prototype.onMouseMove = function(e) {\n        if (!this.oldmouse) {\n            return;\n        }\n\n        var m = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]];\n\n        var v = this.value;\n        v -= (m[1] - this.oldmouse[1]) * 0.01;\n        if (v > 1.0) {\n            v = 1.0;\n        } else if (v < 0.0) {\n            v = 0.0;\n        }\n        this.value = v;\n        this.properties.value =\n            this.properties.min +\n            (this.properties.max - this.properties.min) * this.value;\n        this.oldmouse = m;\n        this.setDirtyCanvas(true);\n    };\n\n    WidgetKnob.prototype.onMouseUp = function(e) {\n        if (this.oldmouse) {\n            this.oldmouse = null;\n            this.captureInput(false);\n        }\n    };\n\n    WidgetKnob.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"min\" || name == \"max\" || name == \"value\") {\n            this.properties[name] = parseFloat(value);\n            return true; //block\n        }\n    };\n\n    LiteGraph.registerNodeType(\"widget/knob\", WidgetKnob);\n\n    //Show value inside the debug console\n    function WidgetSliderGUI() {\n        this.addOutput(\"\", \"number\");\n        this.properties = {\n            value: 0.5,\n            min: 0,\n            max: 1,\n            text: \"V\"\n        };\n        var that = this;\n        this.size = [140, 40];\n        this.slider = this.addWidget(\n            \"slider\",\n            \"V\",\n            this.properties.value,\n            function(v) {\n                that.properties.value = v;\n            },\n            this.properties\n        );\n        this.widgets_up = true;\n    }\n\n    WidgetSliderGUI.title = \"Inner Slider\";\n\n    WidgetSliderGUI.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"value\") {\n            this.slider.value = value;\n        }\n    };\n\n    WidgetSliderGUI.prototype.onExecute = function() {\n        this.setOutputData(0, this.properties.value);\n    };\n\n    LiteGraph.registerNodeType(\"widget/internal_slider\", WidgetSliderGUI);\n\n    //Widget H SLIDER\n    function WidgetHSlider() {\n        this.size = [160, 26];\n        this.addOutput(\"\", \"number\");\n        this.properties = { color: \"#7AF\", min: 0, max: 1, value: 0.5 };\n        this.value = -1;\n    }\n\n    WidgetHSlider.title = \"H.Slider\";\n    WidgetHSlider.desc = \"Linear slider controller\";\n\n    WidgetHSlider.prototype.onDrawForeground = function(ctx) {\n        if (this.value == -1) {\n            this.value =\n                (this.properties.value - this.properties.min) /\n                (this.properties.max - this.properties.min);\n        }\n\n        //border\n        ctx.globalAlpha = 1;\n        ctx.lineWidth = 1;\n        ctx.fillStyle = \"#000\";\n        ctx.fillRect(2, 2, this.size[0] - 4, this.size[1] - 4);\n\n        ctx.fillStyle = this.properties.color;\n        ctx.beginPath();\n        ctx.rect(4, 4, (this.size[0] - 8) * this.value, this.size[1] - 8);\n        ctx.fill();\n    };\n\n    WidgetHSlider.prototype.onExecute = function() {\n        this.properties.value =\n            this.properties.min +\n            (this.properties.max - this.properties.min) * this.value;\n        this.setOutputData(0, this.properties.value);\n        this.boxcolor = LiteGraph.colorToString([\n            this.value,\n            this.value,\n            this.value\n        ]);\n    };\n\n    WidgetHSlider.prototype.onMouseDown = function(e) {\n        if (e.canvasY - this.pos[1] < 0) {\n            return false;\n        }\n\n        this.oldmouse = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]];\n        this.captureInput(true);\n        return true;\n    };\n\n    WidgetHSlider.prototype.onMouseMove = function(e) {\n        if (!this.oldmouse) {\n            return;\n        }\n\n        var m = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]];\n\n        var v = this.value;\n        var delta = m[0] - this.oldmouse[0];\n        v += delta / this.size[0];\n        if (v > 1.0) {\n            v = 1.0;\n        } else if (v < 0.0) {\n            v = 0.0;\n        }\n\n        this.value = v;\n\n        this.oldmouse = m;\n        this.setDirtyCanvas(true);\n    };\n\n    WidgetHSlider.prototype.onMouseUp = function(e) {\n        this.oldmouse = null;\n        this.captureInput(false);\n    };\n\n    WidgetHSlider.prototype.onMouseLeave = function(e) {\n        //this.oldmouse = null;\n    };\n\n    LiteGraph.registerNodeType(\"widget/hslider\", WidgetHSlider);\n\n    function WidgetProgress() {\n        this.size = [160, 26];\n        this.addInput(\"\", \"number\");\n        this.properties = { min: 0, max: 1, value: 0, color: \"#AAF\" };\n    }\n\n    WidgetProgress.title = \"Progress\";\n    WidgetProgress.desc = \"Shows data in linear progress\";\n\n    WidgetProgress.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v != undefined) {\n            this.properties[\"value\"] = v;\n        }\n    };\n\n    WidgetProgress.prototype.onDrawForeground = function(ctx) {\n        //border\n        ctx.lineWidth = 1;\n        ctx.fillStyle = this.properties.color;\n        var v =\n            (this.properties.value - this.properties.min) /\n            (this.properties.max - this.properties.min);\n        v = Math.min(1, v);\n        v = Math.max(0, v);\n        ctx.fillRect(2, 2, (this.size[0] - 4) * v, this.size[1] - 4);\n    };\n\n    LiteGraph.registerNodeType(\"widget/progress\", WidgetProgress);\n\n    function WidgetText() {\n        this.addInputs(\"\", 0);\n        this.properties = {\n            value: \"...\",\n            font: \"Arial\",\n            fontsize: 18,\n            color: \"#AAA\",\n            align: \"left\",\n            glowSize: 0,\n            decimals: 1\n        };\n    }\n\n    WidgetText.title = \"Text\";\n    WidgetText.desc = \"Shows the input value\";\n    WidgetText.widgets = [\n        { name: \"resize\", text: \"Resize box\", type: \"button\" },\n        { name: \"led_text\", text: \"LED\", type: \"minibutton\" },\n        { name: \"normal_text\", text: \"Normal\", type: \"minibutton\" }\n    ];\n\n    WidgetText.prototype.onDrawForeground = function(ctx) {\n        //ctx.fillStyle=\"#000\";\n        //ctx.fillRect(0,0,100,60);\n        ctx.fillStyle = this.properties[\"color\"];\n        var v = this.properties[\"value\"];\n\n        if (this.properties[\"glowSize\"]) {\n            ctx.shadowColor = this.properties.color;\n            ctx.shadowOffsetX = 0;\n            ctx.shadowOffsetY = 0;\n            ctx.shadowBlur = this.properties[\"glowSize\"];\n        } else {\n            ctx.shadowColor = \"transparent\";\n        }\n\n        var fontsize = this.properties[\"fontsize\"];\n\n        ctx.textAlign = this.properties[\"align\"];\n        ctx.font = fontsize.toString() + \"px \" + this.properties[\"font\"];\n        this.str =\n            typeof v == \"number\" ? v.toFixed(this.properties[\"decimals\"]) : v;\n\n        if (typeof this.str == \"string\") {\n            var lines = this.str.replace(/[\\r\\n]/g, \"\\\\n\").split(\"\\\\n\");\n            for (var i=0; i < lines.length; i++) {\n                ctx.fillText(\n                    lines[i],\n                    this.properties[\"align\"] == \"left\" ? 15 : this.size[0] - 15,\n                    fontsize * -0.15 + fontsize * (parseInt(i) + 1)\n                );\n            }\n        }\n\n        ctx.shadowColor = \"transparent\";\n        this.last_ctx = ctx;\n        ctx.textAlign = \"left\";\n    };\n\n    WidgetText.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v != null) {\n            this.properties[\"value\"] = v;\n        }\n        //this.setDirtyCanvas(true);\n    };\n\n    WidgetText.prototype.resize = function() {\n        if (!this.last_ctx) {\n            return;\n        }\n\n        var lines = this.str.split(\"\\\\n\");\n        this.last_ctx.font =\n            this.properties[\"fontsize\"] + \"px \" + this.properties[\"font\"];\n        var max = 0;\n        for (var i=0; i < lines.length; i++) {\n            var w = this.last_ctx.measureText(lines[i]).width;\n            if (max < w) {\n                max = w;\n            }\n        }\n        this.size[0] = max + 20;\n        this.size[1] = 4 + lines.length * this.properties[\"fontsize\"];\n\n        this.setDirtyCanvas(true);\n    };\n\n    WidgetText.prototype.onPropertyChanged = function(name, value) {\n        this.properties[name] = value;\n        this.str = typeof value == \"number\" ? value.toFixed(3) : value;\n        //this.resize();\n        return true;\n    };\n\n    LiteGraph.registerNodeType(\"widget/text\", WidgetText);\n\n    function WidgetPanel() {\n        this.size = [200, 100];\n        this.properties = {\n            borderColor: \"#ffffff\",\n            bgcolorTop: \"#f0f0f0\",\n            bgcolorBottom: \"#e0e0e0\",\n            shadowSize: 2,\n            borderRadius: 3\n        };\n    }\n\n    WidgetPanel.title = \"Panel\";\n    WidgetPanel.desc = \"Non interactive panel\";\n    WidgetPanel.widgets = [{ name: \"update\", text: \"Update\", type: \"button\" }];\n\n    WidgetPanel.prototype.createGradient = function(ctx) {\n        if (\n            this.properties[\"bgcolorTop\"] == \"\" ||\n            this.properties[\"bgcolorBottom\"] == \"\"\n        ) {\n            this.lineargradient = 0;\n            return;\n        }\n\n        this.lineargradient = ctx.createLinearGradient(0, 0, 0, this.size[1]);\n        this.lineargradient.addColorStop(0, this.properties[\"bgcolorTop\"]);\n        this.lineargradient.addColorStop(1, this.properties[\"bgcolorBottom\"]);\n    };\n\n    WidgetPanel.prototype.onDrawForeground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        if (this.lineargradient == null) {\n            this.createGradient(ctx);\n        }\n\n        if (!this.lineargradient) {\n            return;\n        }\n\n        ctx.lineWidth = 1;\n        ctx.strokeStyle = this.properties[\"borderColor\"];\n        //ctx.fillStyle = \"#ebebeb\";\n        ctx.fillStyle = this.lineargradient;\n\n        if (this.properties[\"shadowSize\"]) {\n            ctx.shadowColor = \"#000\";\n            ctx.shadowOffsetX = 0;\n            ctx.shadowOffsetY = 0;\n            ctx.shadowBlur = this.properties[\"shadowSize\"];\n        } else {\n            ctx.shadowColor = \"transparent\";\n        }\n\n        ctx.roundRect(\n            0,\n            0,\n            this.size[0] - 1,\n            this.size[1] - 1,\n            this.properties[\"shadowSize\"]\n        );\n        ctx.fill();\n        ctx.shadowColor = \"transparent\";\n        ctx.stroke();\n    };\n\n    LiteGraph.registerNodeType(\"widget/panel\", WidgetPanel);\n})(this);\n"
  },
  {
    "path": "src/nodes/logic.js",
    "content": "(function(global) {\n    var LiteGraph = global.LiteGraph;\n\n    function Selector() {\n        this.addInput(\"sel\", \"number\");\n        this.addInput(\"A\");\n        this.addInput(\"B\");\n        this.addInput(\"C\");\n        this.addInput(\"D\");\n        this.addOutput(\"out\");\n\n        this.selected = 0;\n    }\n\n    Selector.title = \"Selector\";\n    Selector.desc = \"selects an output\";\n\n    Selector.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n        ctx.fillStyle = \"#AFB\";\n        var y = (this.selected + 1) * LiteGraph.NODE_SLOT_HEIGHT + 6;\n        ctx.beginPath();\n        ctx.moveTo(50, y);\n        ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT);\n        ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5);\n        ctx.fill();\n    };\n\n    Selector.prototype.onExecute = function() {\n        var sel = this.getInputData(0);\n        if (sel == null || sel.constructor !== Number)\n            sel = 0;\n        this.selected = sel = Math.round(sel) % (this.inputs.length - 1);\n        var v = this.getInputData(sel + 1);\n        if (v !== undefined) {\n            this.setOutputData(0, v);\n        }\n    };\n\n    Selector.prototype.onGetInputs = function() {\n        return [[\"E\", 0], [\"F\", 0], [\"G\", 0], [\"H\", 0]];\n    };\n\n    LiteGraph.registerNodeType(\"logic/selector\", Selector);\n\n    function Sequence() {\n        this.properties = {\n            sequence: \"A,B,C\"\n        };\n        this.addInput(\"index\", \"number\");\n        this.addInput(\"seq\");\n        this.addOutput(\"out\");\n\n        this.index = 0;\n        this.values = this.properties.sequence.split(\",\");\n    }\n\n    Sequence.title = \"Sequence\";\n    Sequence.desc = \"select one element from a sequence from a string\";\n\n    Sequence.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"sequence\") {\n            this.values = value.split(\",\");\n        }\n    };\n\n    Sequence.prototype.onExecute = function() {\n        var seq = this.getInputData(1);\n        if (seq && seq != this.current_sequence) {\n            this.values = seq.split(\",\");\n            this.current_sequence = seq;\n        }\n        var index = this.getInputData(0);\n        if (index == null) {\n            index = 0;\n        }\n        this.index = index = Math.round(index) % this.values.length;\n\n        this.setOutputData(0, this.values[index]);\n    };\n\n    LiteGraph.registerNodeType(\"logic/sequence\", Sequence);\n\t\n    \n    function logicAnd(){\n        this.properties = { };\n        this.addInput(\"a\", \"boolean\");\n        this.addInput(\"b\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicAnd.title = \"AND\";\n    logicAnd.desc = \"Return true if all inputs are true\";\n    logicAnd.prototype.onExecute = function() {\n        var ret = true;\n        for (var inX in this.inputs){\n            if (!this.getInputData(inX)){\n                var ret = false;\n                break;\n            }\n        }\n        this.setOutputData(0, ret);\n    };\n    logicAnd.prototype.onGetInputs = function() {\n        return [\n            [\"and\", \"boolean\"]\n        ];\n    };\n    LiteGraph.registerNodeType(\"logic/AND\", logicAnd);\n    \n    \n    function logicOr(){\n        this.properties = { };\n        this.addInput(\"a\", \"boolean\");\n        this.addInput(\"b\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicOr.title = \"OR\";\n    logicOr.desc = \"Return true if at least one input is true\";\n    logicOr.prototype.onExecute = function() {\n        var ret = false;\n        for (var inX in this.inputs){\n            if (this.getInputData(inX)){\n                ret = true;\n                break;\n            }\n        }\n        this.setOutputData(0, ret);\n    };\n    logicOr.prototype.onGetInputs = function() {\n        return [\n            [\"or\", \"boolean\"]\n        ];\n    };\n    LiteGraph.registerNodeType(\"logic/OR\", logicOr);\n    \n    \n    function logicNot(){\n        this.properties = { };\n        this.addInput(\"in\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicNot.title = \"NOT\";\n    logicNot.desc = \"Return the logical negation\";\n    logicNot.prototype.onExecute = function() {\n        var ret = !this.getInputData(0);\n        this.setOutputData(0, ret);\n    };\n    LiteGraph.registerNodeType(\"logic/NOT\", logicNot);\n    \n    \n    function logicCompare(){\n        this.properties = { };\n        this.addInput(\"a\", \"boolean\");\n        this.addInput(\"b\", \"boolean\");\n        this.addOutput(\"out\", \"boolean\");\n    }\n    logicCompare.title = \"bool == bool\";\n    logicCompare.desc = \"Compare for logical equality\";\n    logicCompare.prototype.onExecute = function() {\n        var last = null;\n        var ret = true;\n        for (var inX in this.inputs){\n            if (last === null) last = this.getInputData(inX);\n            else\n                if (last != this.getInputData(inX)){\n                    ret = false;\n                    break;\n                }\n        }\n        this.setOutputData(0, ret);\n    };\n    logicCompare.prototype.onGetInputs = function() {\n        return [\n            [\"bool\", \"boolean\"]\n        ];\n    };\n    LiteGraph.registerNodeType(\"logic/CompareBool\", logicCompare);\n    \n    \n    function logicBranch(){\n        this.properties = { };\n        this.addInput(\"onTrigger\", LiteGraph.ACTION);\n        this.addInput(\"condition\", \"boolean\");\n        this.addOutput(\"true\", LiteGraph.EVENT);\n        this.addOutput(\"false\", LiteGraph.EVENT);\n        this.mode = LiteGraph.ON_TRIGGER;\n    }\n    logicBranch.title = \"Branch\";\n    logicBranch.desc = \"Branch execution on condition\";\n    logicBranch.prototype.onExecute = function(param, options) {\n        var condtition = this.getInputData(1);\n        if (condtition){\n            this.triggerSlot(0);\n        }else{\n            this.triggerSlot(1);\n        }\n    };\n    LiteGraph.registerNodeType(\"logic/IF\", logicBranch);\n})(this);\n"
  },
  {
    "path": "src/nodes/math.js",
    "content": "(function(global) {\n    var LiteGraph = global.LiteGraph;\n\n    //Converter\n    function Converter() {\n        this.addInput(\"in\", 0);\n\t\tthis.addOutput(\"out\", 0);\n        this.size = [80, 30];\n    }\n\n    Converter.title = \"Converter\";\n    Converter.desc = \"type A to type B\";\n\n    Converter.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        if (this.outputs) {\n            for (var i = 0; i < this.outputs.length; i++) {\n                var output = this.outputs[i];\n                if (!output.links || !output.links.length) {\n                    continue;\n                }\n\n                var result = null;\n                switch (output.name) {\n                    case \"number\":\n                        result = v.length ? v[0] : parseFloat(v);\n                        break;\n                    case \"vec2\":\n                    case \"vec3\":\n                    case \"vec4\":\n                        var result = null;\n                        var count = 1;\n                        switch (output.name) {\n                            case \"vec2\":\n                                count = 2;\n                                break;\n                            case \"vec3\":\n                                count = 3;\n                                break;\n                            case \"vec4\":\n                                count = 4;\n                                break;\n                        }\n\n                        var result = new Float32Array(count);\n                        if (v.length) {\n                            for (\n                                var j = 0;\n                                j < v.length && j < result.length;\n                                j++\n                            ) {\n                                result[j] = v[j];\n                            }\n                        } else {\n                            result[0] = parseFloat(v);\n                        }\n                        break;\n                }\n                this.setOutputData(i, result);\n            }\n        }\n    };\n\n    Converter.prototype.onGetOutputs = function() {\n        return [\n            [\"number\", \"number\"],\n            [\"vec2\", \"vec2\"],\n            [\"vec3\", \"vec3\"],\n            [\"vec4\", \"vec4\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/converter\", Converter);\n\n    //Bypass\n    function Bypass() {\n        this.addInput(\"in\");\n        this.addOutput(\"out\");\n        this.size = [80, 30];\n    }\n\n    Bypass.title = \"Bypass\";\n    Bypass.desc = \"removes the type\";\n\n    Bypass.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        this.setOutputData(0, v);\n    };\n\n    LiteGraph.registerNodeType(\"math/bypass\", Bypass);\n\n    function ToNumber() {\n        this.addInput(\"in\");\n        this.addOutput(\"out\");\n    }\n\n    ToNumber.title = \"to Number\";\n    ToNumber.desc = \"Cast to number\";\n\n    ToNumber.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        this.setOutputData(0, Number(v));\n    };\n\n    LiteGraph.registerNodeType(\"math/to_number\", ToNumber);\n\n    function MathRange() {\n        this.addInput(\"in\", \"number\", { locked: true });\n        this.addOutput(\"out\", \"number\", { locked: true });\n        this.addOutput(\"clamped\", \"number\", { locked: true });\n\n        this.addProperty(\"in\", 0);\n        this.addProperty(\"in_min\", 0);\n        this.addProperty(\"in_max\", 1);\n        this.addProperty(\"out_min\", 0);\n        this.addProperty(\"out_max\", 1);\n\n        this.size = [120, 50];\n    }\n\n    MathRange.title = \"Range\";\n    MathRange.desc = \"Convert a number from one range to another\";\n\n    MathRange.prototype.getTitle = function() {\n        if (this.flags.collapsed) {\n            return (this._last_v || 0).toFixed(2);\n        }\n        return this.title;\n    };\n\n    MathRange.prototype.onExecute = function() {\n        if (this.inputs) {\n            for (var i = 0; i < this.inputs.length; i++) {\n                var input = this.inputs[i];\n                var v = this.getInputData(i);\n                if (v === undefined) {\n                    continue;\n                }\n                this.properties[input.name] = v;\n            }\n        }\n\n        var v = this.properties[\"in\"];\n        if (v === undefined || v === null || v.constructor !== Number) {\n            v = 0;\n        }\n\n        var in_min = this.properties.in_min;\n        var in_max = this.properties.in_max;\n        var out_min = this.properties.out_min;\n        var out_max = this.properties.out_max;\n\t\t/*\n\t\tif( in_min > in_max )\n\t\t{\n\t\t\tin_min = in_max;\n\t\t\tin_max = this.properties.in_min;\n\t\t}\n\t\tif( out_min > out_max )\n\t\t{\n\t\t\tout_min = out_max;\n\t\t\tout_max = this.properties.out_min;\n\t\t}\n\t\t*/\n\n        this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min;\n        this.setOutputData(0, this._last_v);\n        this.setOutputData(1, clamp( this._last_v, out_min, out_max ));\n    };\n\n    MathRange.prototype.onDrawBackground = function(ctx) {\n        //show the current value\n        if (this._last_v) {\n            this.outputs[0].label = this._last_v.toFixed(3);\n        } else {\n            this.outputs[0].label = \"?\";\n        }\n    };\n\n    MathRange.prototype.onGetInputs = function() {\n        return [\n            [\"in_min\", \"number\"],\n            [\"in_max\", \"number\"],\n            [\"out_min\", \"number\"],\n            [\"out_max\", \"number\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/range\", MathRange);\n\n    function MathRand() {\n        this.addOutput(\"value\", \"number\");\n        this.addProperty(\"min\", 0);\n        this.addProperty(\"max\", 1);\n        this.size = [80, 30];\n    }\n\n    MathRand.title = \"Rand\";\n    MathRand.desc = \"Random number\";\n\n    MathRand.prototype.onExecute = function() {\n        if (this.inputs) {\n            for (var i = 0; i < this.inputs.length; i++) {\n                var input = this.inputs[i];\n                var v = this.getInputData(i);\n                if (v === undefined) {\n                    continue;\n                }\n                this.properties[input.name] = v;\n            }\n        }\n\n        var min = this.properties.min;\n        var max = this.properties.max;\n        this._last_v = Math.random() * (max - min) + min;\n        this.setOutputData(0, this._last_v);\n    };\n\n    MathRand.prototype.onDrawBackground = function(ctx) {\n        //show the current value\n        this.outputs[0].label = (this._last_v || 0).toFixed(3);\n    };\n\n    MathRand.prototype.onGetInputs = function() {\n        return [[\"min\", \"number\"], [\"max\", \"number\"]];\n    };\n\n    LiteGraph.registerNodeType(\"math/rand\", MathRand);\n\n    //basic continuous noise\n    function MathNoise() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.addProperty(\"min\", 0);\n        this.addProperty(\"max\", 1);\n        this.addProperty(\"smooth\", true);\n        this.addProperty(\"seed\", 0);\n        this.addProperty(\"octaves\", 1);\n        this.addProperty(\"persistence\", 0.8);\n        this.addProperty(\"speed\", 1);\n        this.size = [90, 30];\n    }\n\n    MathNoise.title = \"Noise\";\n    MathNoise.desc = \"Random number with temporal continuity\";\n    MathNoise.data = null;\n\n    MathNoise.getValue = function(f, smooth) {\n        if (!MathNoise.data) {\n            MathNoise.data = new Float32Array(1024);\n            for (var i = 0; i < MathNoise.data.length; ++i) {\n                MathNoise.data[i] = Math.random();\n            }\n        }\n        f = f % 1024;\n        if (f < 0) {\n            f += 1024;\n        }\n        var f_min = Math.floor(f);\n        var f = f - f_min;\n        var r1 = MathNoise.data[f_min];\n        var r2 = MathNoise.data[f_min == 1023 ? 0 : f_min + 1];\n        if (smooth) {\n            f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n        }\n        return r1 * (1 - f) + r2 * f;\n    };\n\n    MathNoise.prototype.onExecute = function() {\n        var f = this.getInputData(0) || 0;\n\t\tvar iterations = this.properties.octaves || 1;\n\t\tvar r = 0;\n\t\tvar amp = 1;\n\t\tvar seed = this.properties.seed || 0;\n\t\tf += seed;\n\t\tvar speed = this.properties.speed || 1;\n\t\tvar total_amp = 0;\n\t\tfor(var i = 0; i < iterations; ++i)\n\t\t{\n\t\t\tr += MathNoise.getValue(f * (1+i) * speed, this.properties.smooth) * amp;\n\t\t\ttotal_amp += amp;\n\t\t\tamp *= this.properties.persistence;\n\t\t\tif(amp < 0.001)\n\t\t\t\tbreak;\n\t\t}\n\t\tr /= total_amp;\n        var min = this.properties.min;\n        var max = this.properties.max;\n        this._last_v = r * (max - min) + min;\n        this.setOutputData(0, this._last_v);\n    };\n\n    MathNoise.prototype.onDrawBackground = function(ctx) {\n        //show the current value\n        this.outputs[0].label = (this._last_v || 0).toFixed(3);\n    };\n\n    LiteGraph.registerNodeType(\"math/noise\", MathNoise);\n\n    //generates spikes every random time\n    function MathSpikes() {\n        this.addOutput(\"out\", \"number\");\n        this.addProperty(\"min_time\", 1);\n        this.addProperty(\"max_time\", 2);\n        this.addProperty(\"duration\", 0.2);\n        this.size = [90, 30];\n        this._remaining_time = 0;\n        this._blink_time = 0;\n    }\n\n    MathSpikes.title = \"Spikes\";\n    MathSpikes.desc = \"spike every random time\";\n\n    MathSpikes.prototype.onExecute = function() {\n        var dt = this.graph.elapsed_time; //in secs\n\n        this._remaining_time -= dt;\n        this._blink_time -= dt;\n\n        var v = 0;\n        if (this._blink_time > 0) {\n            var f = this._blink_time / this.properties.duration;\n            v = 1 / (Math.pow(f * 8 - 4, 4) + 1);\n        }\n\n        if (this._remaining_time < 0) {\n            this._remaining_time =\n                Math.random() *\n                    (this.properties.max_time - this.properties.min_time) +\n                this.properties.min_time;\n            this._blink_time = this.properties.duration;\n            this.boxcolor = \"#FFF\";\n        } else {\n            this.boxcolor = \"#000\";\n        }\n        this.setOutputData(0, v);\n    };\n\n    LiteGraph.registerNodeType(\"math/spikes\", MathSpikes);\n\n    //Math clamp\n    function MathClamp() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n        this.addProperty(\"min\", 0);\n        this.addProperty(\"max\", 1);\n    }\n\n    MathClamp.title = \"Clamp\";\n    MathClamp.desc = \"Clamp number between min and max\";\n    //MathClamp.filter = \"shader\";\n\n    MathClamp.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        v = Math.max(this.properties.min, v);\n        v = Math.min(this.properties.max, v);\n        this.setOutputData(0, v);\n    };\n\n    MathClamp.prototype.getCode = function(lang) {\n        var code = \"\";\n        if (this.isInputConnected(0)) {\n            code +=\n                \"clamp({{0}},\" +\n                this.properties.min +\n                \",\" +\n                this.properties.max +\n                \")\";\n        }\n        return code;\n    };\n\n    LiteGraph.registerNodeType(\"math/clamp\", MathClamp);\n\n    //Math ABS\n    function MathLerp() {\n        this.properties = { f: 0.5 };\n        this.addInput(\"A\", \"number\");\n        this.addInput(\"B\", \"number\");\n\n        this.addOutput(\"out\", \"number\");\n    }\n\n    MathLerp.title = \"Lerp\";\n    MathLerp.desc = \"Linear Interpolation\";\n\n    MathLerp.prototype.onExecute = function() {\n        var v1 = this.getInputData(0);\n        if (v1 == null) {\n            v1 = 0;\n        }\n        var v2 = this.getInputData(1);\n        if (v2 == null) {\n            v2 = 0;\n        }\n\n        var f = this.properties.f;\n\n        var _f = this.getInputData(2);\n        if (_f !== undefined) {\n            f = _f;\n        }\n\n        this.setOutputData(0, v1 * (1 - f) + v2 * f);\n    };\n\n    MathLerp.prototype.onGetInputs = function() {\n        return [[\"f\", \"number\"]];\n    };\n\n    LiteGraph.registerNodeType(\"math/lerp\", MathLerp);\n\n    //Math ABS\n    function MathAbs() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n    }\n\n    MathAbs.title = \"Abs\";\n    MathAbs.desc = \"Absolute\";\n\n    MathAbs.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        this.setOutputData(0, Math.abs(v));\n    };\n\n    LiteGraph.registerNodeType(\"math/abs\", MathAbs);\n\n    //Math Floor\n    function MathFloor() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n    }\n\n    MathFloor.title = \"Floor\";\n    MathFloor.desc = \"Floor number to remove fractional part\";\n\n    MathFloor.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        this.setOutputData(0, Math.floor(v));\n    };\n\n    LiteGraph.registerNodeType(\"math/floor\", MathFloor);\n\n    //Math frac\n    function MathFrac() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n    }\n\n    MathFrac.title = \"Frac\";\n    MathFrac.desc = \"Returns fractional part\";\n\n    MathFrac.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n        this.setOutputData(0, v % 1);\n    };\n\n    LiteGraph.registerNodeType(\"math/frac\", MathFrac);\n\n    //Math Floor\n    function MathSmoothStep() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n        this.properties = { A: 0, B: 1 };\n    }\n\n    MathSmoothStep.title = \"Smoothstep\";\n    MathSmoothStep.desc = \"Smoothstep\";\n\n    MathSmoothStep.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v === undefined) {\n            return;\n        }\n\n        var edge0 = this.properties.A;\n        var edge1 = this.properties.B;\n\n        // Scale, bias and saturate x to 0..1 range\n        v = clamp((v - edge0) / (edge1 - edge0), 0.0, 1.0);\n        // Evaluate polynomial\n        v = v * v * (3 - 2 * v);\n\n        this.setOutputData(0, v);\n    };\n\n    LiteGraph.registerNodeType(\"math/smoothstep\", MathSmoothStep);\n\n    //Math scale\n    function MathScale() {\n        this.addInput(\"in\", \"number\", { label: \"\" });\n        this.addOutput(\"out\", \"number\", { label: \"\" });\n        this.size = [80, 30];\n        this.addProperty(\"factor\", 1);\n    }\n\n    MathScale.title = \"Scale\";\n    MathScale.desc = \"v * factor\";\n\n    MathScale.prototype.onExecute = function() {\n        var value = this.getInputData(0);\n        if (value != null) {\n            this.setOutputData(0, value * this.properties.factor);\n        }\n    };\n\n    LiteGraph.registerNodeType(\"math/scale\", MathScale);\n\n\t//Gate\n\tfunction Gate() {\n\t\tthis.addInput(\"v\",\"boolean\");\n\t\tthis.addInput(\"A\");\n\t\tthis.addInput(\"B\");\n\t\tthis.addOutput(\"out\");\n\t}\n\n\tGate.title = \"Gate\";\n\tGate.desc = \"if v is true, then outputs A, otherwise B\";\n\n\tGate.prototype.onExecute = function() {\n\t\tvar v = this.getInputData(0);\n\t\tthis.setOutputData(0, this.getInputData( v ? 1 : 2 ));\n\t};\n\n\tLiteGraph.registerNodeType(\"math/gate\", Gate);\n\n\n    //Math Average\n    function MathAverageFilter() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.size = [80, 30];\n        this.addProperty(\"samples\", 10);\n        this._values = new Float32Array(10);\n        this._current = 0;\n    }\n\n    MathAverageFilter.title = \"Average\";\n    MathAverageFilter.desc = \"Average Filter\";\n\n    MathAverageFilter.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            v = 0;\n        }\n\n        var num_samples = this._values.length;\n\n        this._values[this._current % num_samples] = v;\n        this._current += 1;\n        if (this._current > num_samples) {\n            this._current = 0;\n        }\n\n        var avr = 0;\n        for (var i = 0; i < num_samples; ++i) {\n            avr += this._values[i];\n        }\n\n        this.setOutputData(0, avr / num_samples);\n    };\n\n    MathAverageFilter.prototype.onPropertyChanged = function(name, value) {\n        if (value < 1) {\n            value = 1;\n        }\n        this.properties.samples = Math.round(value);\n        var old = this._values;\n\n        this._values = new Float32Array(this.properties.samples);\n        if (old.length <= this._values.length) {\n            this._values.set(old);\n        } else {\n            this._values.set(old.subarray(0, this._values.length));\n        }\n    };\n\n    LiteGraph.registerNodeType(\"math/average\", MathAverageFilter);\n\n    //Math\n    function MathTendTo() {\n        this.addInput(\"in\", \"number\");\n        this.addOutput(\"out\", \"number\");\n        this.addProperty(\"factor\", 0.1);\n        this.size = [80, 30];\n        this._value = null;\n    }\n\n    MathTendTo.title = \"TendTo\";\n    MathTendTo.desc = \"moves the output value always closer to the input\";\n\n    MathTendTo.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            v = 0;\n        }\n        var f = this.properties.factor;\n        if (this._value == null) {\n            this._value = v;\n        } else {\n            this._value = this._value * (1 - f) + v * f;\n        }\n        this.setOutputData(0, this._value);\n    };\n\n    LiteGraph.registerNodeType(\"math/tendTo\", MathTendTo);\n\n    //Math operation\n    function MathOperation() {\n        this.addInput(\"A\", \"number,array,object\");\n        this.addInput(\"B\", \"number\");\n        this.addOutput(\"=\", \"number\");\n        this.addProperty(\"A\", 1);\n        this.addProperty(\"B\", 1);\n        this.addProperty(\"OP\", \"+\", \"enum\", { values: MathOperation.values });\n\t\tthis._func = MathOperation.funcs[this.properties.OP];\n\t\tthis._result = []; //only used for arrays\n    }\n\n    MathOperation.values = [\"+\", \"-\", \"*\", \"/\", \"%\", \"^\", \"max\", \"min\"];\n    MathOperation.funcs = {\n        \"+\": function(A,B) { return A + B; },\n        \"-\": function(A,B) { return A - B; },\n        \"x\": function(A,B) { return A * B; },\n        \"X\": function(A,B) { return A * B; },\n        \"*\": function(A,B) { return A * B; },\n        \"/\": function(A,B) { return A / B; },\n        \"%\": function(A,B) { return A % B; },\n        \"^\": function(A,B) { return Math.pow(A, B); },\n        \"max\": function(A,B) { return Math.max(A, B); },\n        \"min\": function(A,B) { return Math.min(A, B); }\n    };\n\n\tMathOperation.title = \"Operation\";\n    MathOperation.desc = \"Easy math operators\";\n    MathOperation[\"@OP\"] = {\n        type: \"enum\",\n        title: \"operation\",\n        values: MathOperation.values\n    };\n    MathOperation.size = [100, 60];\n\n    MathOperation.prototype.getTitle = function() {\n\t\tif(this.properties.OP == \"max\" || this.properties.OP == \"min\")\n\t\t\treturn this.properties.OP + \"(A,B)\";\n        return \"A \" + this.properties.OP + \" B\";\n    };\n\n    MathOperation.prototype.setValue = function(v) {\n        if (typeof v == \"string\") {\n            v = parseFloat(v);\n        }\n        this.properties[\"value\"] = v;\n    };\n\n    MathOperation.prototype.onPropertyChanged = function(name, value)\n\t{\n\t\tif (name != \"OP\")\n\t\t\treturn;\n        this._func = MathOperation.funcs[this.properties.OP];\n        if(!this._func)\n        {\n            console.warn(\"Unknown operation: \" + this.properties.OP);\n            this._func = function(A) { return A; };\n        }\n\t}\n\n    MathOperation.prototype.onExecute = function() {\n        var A = this.getInputData(0);\n        var B = this.getInputData(1);\n        if ( A != null ) {\n\t\t\tif( A.constructor === Number )\n\t            this.properties[\"A\"] = A;\n        } else {\n            A = this.properties[\"A\"];\n        }\n\n        if (B != null) {\n            this.properties[\"B\"] = B;\n        } else {\n            B = this.properties[\"B\"];\n        }\n\n        var func = MathOperation.funcs[this.properties.OP];\n\n\t\tvar result;\n\t\tif(A.constructor === Number)\n\t\t{\n\t        result = 0;\n\t\t\tresult = func(A,B);\n\t\t}\n\t\telse if(A.constructor === Array)\n\t\t{\n\t\t\tresult = this._result;\n\t\t\tresult.length = A.length;\n\t\t\tfor(var i = 0; i < A.length; ++i)\n\t\t\t\tresult[i] = func(A[i],B);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tresult = {};\n\t\t\tfor(var i in A)\n\t\t\t\tresult[i] = func(A[i],B);\n\t\t}\n\t    this.setOutputData(0, result);\n    };\n\n    MathOperation.prototype.onDrawBackground = function(ctx) {\n        if (this.flags.collapsed) {\n            return;\n        }\n\n        ctx.font = \"40px Arial\";\n        ctx.fillStyle = \"#666\";\n        ctx.textAlign = \"center\";\n        ctx.fillText(\n            this.properties.OP,\n            this.size[0] * 0.5,\n            (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5\n        );\n        ctx.textAlign = \"left\";\n    };\n\n    LiteGraph.registerNodeType(\"math/operation\", MathOperation);\n\n    LiteGraph.registerSearchboxExtra(\"math/operation\", \"MAX\", {\n        properties: {OP:\"max\"},\n        title: \"MAX()\"\n    });\n\n    LiteGraph.registerSearchboxExtra(\"math/operation\", \"MIN\", {\n        properties: {OP:\"min\"},\n        title: \"MIN()\"\n    });\n\n\n    //Math compare\n    function MathCompare() {\n        this.addInput(\"A\", \"number\");\n        this.addInput(\"B\", \"number\");\n        this.addOutput(\"A==B\", \"boolean\");\n        this.addOutput(\"A!=B\", \"boolean\");\n        this.addProperty(\"A\", 0);\n        this.addProperty(\"B\", 0);\n    }\n\n    MathCompare.title = \"Compare\";\n    MathCompare.desc = \"compares between two values\";\n\n    MathCompare.prototype.onExecute = function() {\n        var A = this.getInputData(0);\n        var B = this.getInputData(1);\n        if (A !== undefined) {\n            this.properties[\"A\"] = A;\n        } else {\n            A = this.properties[\"A\"];\n        }\n\n        if (B !== undefined) {\n            this.properties[\"B\"] = B;\n        } else {\n            B = this.properties[\"B\"];\n        }\n\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            var output = this.outputs[i];\n            if (!output.links || !output.links.length) {\n                continue;\n            }\n            var value;\n            switch (output.name) {\n                case \"A==B\":\n                    value = A == B;\n                    break;\n                case \"A!=B\":\n                    value = A != B;\n                    break;\n                case \"A>B\":\n                    value = A > B;\n                    break;\n                case \"A<B\":\n                    value = A < B;\n                    break;\n                case \"A<=B\":\n                    value = A <= B;\n                    break;\n                case \"A>=B\":\n                    value = A >= B;\n                    break;\n            }\n            this.setOutputData(i, value);\n        }\n    };\n\n    MathCompare.prototype.onGetOutputs = function() {\n        return [\n            [\"A==B\", \"boolean\"],\n            [\"A!=B\", \"boolean\"],\n            [\"A>B\", \"boolean\"],\n            [\"A<B\", \"boolean\"],\n            [\"A>=B\", \"boolean\"],\n            [\"A<=B\", \"boolean\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/compare\", MathCompare);\n\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"==\", {\n        outputs: [[\"A==B\", \"boolean\"]],\n        title: \"A==B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"!=\", {\n        outputs: [[\"A!=B\", \"boolean\"]],\n        title: \"A!=B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \">\", {\n        outputs: [[\"A>B\", \"boolean\"]],\n        title: \"A>B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"<\", {\n        outputs: [[\"A<B\", \"boolean\"]],\n        title: \"A<B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \">=\", {\n        outputs: [[\"A>=B\", \"boolean\"]],\n        title: \"A>=B\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/compare\", \"<=\", {\n        outputs: [[\"A<=B\", \"boolean\"]],\n        title: \"A<=B\"\n    });\n\n    function MathCondition() {\n        this.addInput(\"A\", \"number\");\n        this.addInput(\"B\", \"number\");\n        this.addOutput(\"true\", \"boolean\");\n        this.addOutput(\"false\", \"boolean\");\n        this.addProperty(\"A\", 1);\n        this.addProperty(\"B\", 1);\n        this.addProperty(\"OP\", \">\", \"enum\", { values: MathCondition.values });\n\t\tthis.addWidget(\"combo\",\"Cond.\",this.properties.OP,{ property: \"OP\", values: MathCondition.values } );\n\n        this.size = [80, 60];\n    }\n\n    MathCondition.values = [\">\", \"<\", \"==\", \"!=\", \"<=\", \">=\", \"||\", \"&&\" ];\n    MathCondition[\"@OP\"] = {\n        type: \"enum\",\n        title: \"operation\",\n        values: MathCondition.values\n    };\n\n    MathCondition.title = \"Condition\";\n    MathCondition.desc = \"evaluates condition between A and B\";\n\n    MathCondition.prototype.getTitle = function() {\n        return \"A \" + this.properties.OP + \" B\";\n    };\n\n    MathCondition.prototype.onExecute = function() {\n        var A = this.getInputData(0);\n        if (A === undefined) {\n            A = this.properties.A;\n        } else {\n            this.properties.A = A;\n        }\n\n        var B = this.getInputData(1);\n        if (B === undefined) {\n            B = this.properties.B;\n        } else {\n            this.properties.B = B;\n        }\n\n        var result = true;\n        switch (this.properties.OP) {\n            case \">\":\n                result = A > B;\n                break;\n            case \"<\":\n                result = A < B;\n                break;\n            case \"==\":\n                result = A == B;\n                break;\n            case \"!=\":\n                result = A != B;\n                break;\n            case \"<=\":\n                result = A <= B;\n                break;\n            case \">=\":\n                result = A >= B;\n                break;\n            case \"||\":\n                result = A || B;\n                break;\n            case \"&&\":\n                result = A && B;\n                break;\n        }\n\n        this.setOutputData(0, result);\n        this.setOutputData(1, !result);\n    };\n\n    LiteGraph.registerNodeType(\"math/condition\", MathCondition);\n\n\n    function MathBranch() {\n        this.addInput(\"in\", 0);\n        this.addInput(\"cond\", \"boolean\");\n        this.addOutput(\"true\", 0);\n        this.addOutput(\"false\", 0);\n        this.size = [80, 60];\n    }\n\n    MathBranch.title = \"Branch\";\n    MathBranch.desc = \"If condition is true, outputs IN in true, otherwise in false\";\n\n    MathBranch.prototype.onExecute = function() {\n        var V = this.getInputData(0);\n        var cond = this.getInputData(1);\n\n\t\tif(cond)\n\t\t{\n\t\t\tthis.setOutputData(0, V);\n\t\t\tthis.setOutputData(1, null);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis.setOutputData(0, null);\n\t\t\tthis.setOutputData(1, V);\n\t\t}\n\t}\n\n    LiteGraph.registerNodeType(\"math/branch\", MathBranch);\n\n\n    function MathAccumulate() {\n        this.addInput(\"inc\", \"number\");\n        this.addOutput(\"total\", \"number\");\n        this.addProperty(\"increment\", 1);\n        this.addProperty(\"value\", 0);\n    }\n\n    MathAccumulate.title = \"Accumulate\";\n    MathAccumulate.desc = \"Increments a value every time\";\n\n    MathAccumulate.prototype.onExecute = function() {\n        if (this.properties.value === null) {\n            this.properties.value = 0;\n        }\n\n        var inc = this.getInputData(0);\n        if (inc !== null) {\n            this.properties.value += inc;\n        } else {\n            this.properties.value += this.properties.increment;\n        }\n        this.setOutputData(0, this.properties.value);\n    };\n\n    LiteGraph.registerNodeType(\"math/accumulate\", MathAccumulate);\n\n    //Math Trigonometry\n    function MathTrigonometry() {\n        this.addInput(\"v\", \"number\");\n        this.addOutput(\"sin\", \"number\");\n\n        this.addProperty(\"amplitude\", 1);\n        this.addProperty(\"offset\", 0);\n        this.bgImageUrl = \"nodes/imgs/icon-sin.png\";\n    }\n\n    MathTrigonometry.title = \"Trigonometry\";\n    MathTrigonometry.desc = \"Sin Cos Tan\";\n    //MathTrigonometry.filter = \"shader\";\n\n    MathTrigonometry.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            v = 0;\n        }\n        var amplitude = this.properties[\"amplitude\"];\n        var slot = this.findInputSlot(\"amplitude\");\n        if (slot != -1) {\n            amplitude = this.getInputData(slot);\n        }\n        var offset = this.properties[\"offset\"];\n        slot = this.findInputSlot(\"offset\");\n        if (slot != -1) {\n            offset = this.getInputData(slot);\n        }\n\n        for (var i = 0, l = this.outputs.length; i < l; ++i) {\n            var output = this.outputs[i];\n            var value;\n            switch (output.name) {\n                case \"sin\":\n                    value = Math.sin(v);\n                    break;\n                case \"cos\":\n                    value = Math.cos(v);\n                    break;\n                case \"tan\":\n                    value = Math.tan(v);\n                    break;\n                case \"asin\":\n                    value = Math.asin(v);\n                    break;\n                case \"acos\":\n                    value = Math.acos(v);\n                    break;\n                case \"atan\":\n                    value = Math.atan(v);\n                    break;\n            }\n            this.setOutputData(i, amplitude * value + offset);\n        }\n    };\n\n    MathTrigonometry.prototype.onGetInputs = function() {\n        return [[\"v\", \"number\"], [\"amplitude\", \"number\"], [\"offset\", \"number\"]];\n    };\n\n    MathTrigonometry.prototype.onGetOutputs = function() {\n        return [\n            [\"sin\", \"number\"],\n            [\"cos\", \"number\"],\n            [\"tan\", \"number\"],\n            [\"asin\", \"number\"],\n            [\"acos\", \"number\"],\n            [\"atan\", \"number\"]\n        ];\n    };\n\n    LiteGraph.registerNodeType(\"math/trigonometry\", MathTrigonometry);\n\n    LiteGraph.registerSearchboxExtra(\"math/trigonometry\", \"SIN()\", {\n        outputs: [[\"sin\", \"number\"]],\n        title: \"SIN()\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/trigonometry\", \"COS()\", {\n        outputs: [[\"cos\", \"number\"]],\n        title: \"COS()\"\n    });\n    LiteGraph.registerSearchboxExtra(\"math/trigonometry\", \"TAN()\", {\n        outputs: [[\"tan\", \"number\"]],\n        title: \"TAN()\"\n    });\n\n    //math library for safe math operations without eval\n    function MathFormula() {\n        this.addInput(\"x\", \"number\");\n        this.addInput(\"y\", \"number\");\n        this.addOutput(\"\", \"number\");\n        this.properties = { x: 1.0, y: 1.0, formula: \"x+y\" };\n        this.code_widget = this.addWidget(\n            \"text\",\n            \"F(x,y)\",\n            this.properties.formula,\n            function(v, canvas, node) {\n                node.properties.formula = v;\n            }\n        );\n        this.addWidget(\"toggle\", \"allow\", LiteGraph.allow_scripts, function(v) {\n            LiteGraph.allow_scripts = v;\n        });\n        this._func = null;\n    }\n\n    MathFormula.title = \"Formula\";\n    MathFormula.desc = \"Compute formula\";\n    MathFormula.size = [160, 100];\n\n    MathAverageFilter.prototype.onPropertyChanged = function(name, value) {\n        if (name == \"formula\") {\n            this.code_widget.value = value;\n        }\n    };\n\n    MathFormula.prototype.onExecute = function() {\n        if (!LiteGraph.allow_scripts) {\n            return;\n        }\n\n        var x = this.getInputData(0);\n        var y = this.getInputData(1);\n        if (x != null) {\n            this.properties[\"x\"] = x;\n        } else {\n            x = this.properties[\"x\"];\n        }\n\n        if (y != null) {\n            this.properties[\"y\"] = y;\n        } else {\n            y = this.properties[\"y\"];\n        }\n\n        var f = this.properties[\"formula\"];\n\n        var value;\n        try {\n            if (!this._func || this._func_code != this.properties.formula) {\n                this._func = new Function(\n                    \"x\",\n                    \"y\",\n                    \"TIME\",\n                    \"return \" + this.properties.formula\n                );\n                this._func_code = this.properties.formula;\n            }\n            value = this._func(x, y, this.graph.globaltime);\n            this.boxcolor = null;\n        } catch (err) {\n            this.boxcolor = \"red\";\n        }\n        this.setOutputData(0, value);\n    };\n\n    MathFormula.prototype.getTitle = function() {\n        return this._func_code || \"Formula\";\n    };\n\n    MathFormula.prototype.onDrawBackground = function() {\n        var f = this.properties[\"formula\"];\n        if (this.outputs && this.outputs.length) {\n            this.outputs[0].label = f;\n        }\n    };\n\n    LiteGraph.registerNodeType(\"math/formula\", MathFormula);\n\n    function Math3DVec2ToXY() {\n        this.addInput(\"vec2\", \"vec2\");\n        this.addOutput(\"x\", \"number\");\n        this.addOutput(\"y\", \"number\");\n    }\n\n    Math3DVec2ToXY.title = \"Vec2->XY\";\n    Math3DVec2ToXY.desc = \"vector 2 to components\";\n\n    Math3DVec2ToXY.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        this.setOutputData(0, v[0]);\n        this.setOutputData(1, v[1]);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/vec2-to-xy\", Math3DVec2ToXY);\n\n    function Math3DXYToVec2() {\n        this.addInputs([[\"x\", \"number\"], [\"y\", \"number\"]]);\n        this.addOutput(\"vec2\", \"vec2\");\n        this.properties = { x: 0, y: 0 };\n        this._data = new Float32Array(2);\n    }\n\n    Math3DXYToVec2.title = \"XY->Vec2\";\n    Math3DXYToVec2.desc = \"components to vector2\";\n\n    Math3DXYToVec2.prototype.onExecute = function() {\n        var x = this.getInputData(0);\n        if (x == null) {\n            x = this.properties.x;\n        }\n        var y = this.getInputData(1);\n        if (y == null) {\n            y = this.properties.y;\n        }\n\n        var data = this._data;\n        data[0] = x;\n        data[1] = y;\n\n        this.setOutputData(0, data);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/xy-to-vec2\", Math3DXYToVec2);\n\n    function Math3DVec3ToXYZ() {\n        this.addInput(\"vec3\", \"vec3\");\n        this.addOutput(\"x\", \"number\");\n        this.addOutput(\"y\", \"number\");\n        this.addOutput(\"z\", \"number\");\n    }\n\n    Math3DVec3ToXYZ.title = \"Vec3->XYZ\";\n    Math3DVec3ToXYZ.desc = \"vector 3 to components\";\n\n    Math3DVec3ToXYZ.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        this.setOutputData(0, v[0]);\n        this.setOutputData(1, v[1]);\n        this.setOutputData(2, v[2]);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/vec3-to-xyz\", Math3DVec3ToXYZ);\n\n    function Math3DXYZToVec3() {\n        this.addInputs([[\"x\", \"number\"], [\"y\", \"number\"], [\"z\", \"number\"]]);\n        this.addOutput(\"vec3\", \"vec3\");\n        this.properties = { x: 0, y: 0, z: 0 };\n        this._data = new Float32Array(3);\n    }\n\n    Math3DXYZToVec3.title = \"XYZ->Vec3\";\n    Math3DXYZToVec3.desc = \"components to vector3\";\n\n    Math3DXYZToVec3.prototype.onExecute = function() {\n        var x = this.getInputData(0);\n        if (x == null) {\n            x = this.properties.x;\n        }\n        var y = this.getInputData(1);\n        if (y == null) {\n            y = this.properties.y;\n        }\n        var z = this.getInputData(2);\n        if (z == null) {\n            z = this.properties.z;\n        }\n\n        var data = this._data;\n        data[0] = x;\n        data[1] = y;\n        data[2] = z;\n\n        this.setOutputData(0, data);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/xyz-to-vec3\", Math3DXYZToVec3);\n\n    function Math3DVec4ToXYZW() {\n        this.addInput(\"vec4\", \"vec4\");\n        this.addOutput(\"x\", \"number\");\n        this.addOutput(\"y\", \"number\");\n        this.addOutput(\"z\", \"number\");\n        this.addOutput(\"w\", \"number\");\n    }\n\n    Math3DVec4ToXYZW.title = \"Vec4->XYZW\";\n    Math3DVec4ToXYZW.desc = \"vector 4 to components\";\n\n    Math3DVec4ToXYZW.prototype.onExecute = function() {\n        var v = this.getInputData(0);\n        if (v == null) {\n            return;\n        }\n\n        this.setOutputData(0, v[0]);\n        this.setOutputData(1, v[1]);\n        this.setOutputData(2, v[2]);\n        this.setOutputData(3, v[3]);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/vec4-to-xyzw\", Math3DVec4ToXYZW);\n\n    function Math3DXYZWToVec4() {\n        this.addInputs([\n            [\"x\", \"number\"],\n            [\"y\", \"number\"],\n            [\"z\", \"number\"],\n            [\"w\", \"number\"]\n        ]);\n        this.addOutput(\"vec4\", \"vec4\");\n        this.properties = { x: 0, y: 0, z: 0, w: 0 };\n        this._data = new Float32Array(4);\n    }\n\n    Math3DXYZWToVec4.title = \"XYZW->Vec4\";\n    Math3DXYZWToVec4.desc = \"components to vector4\";\n\n    Math3DXYZWToVec4.prototype.onExecute = function() {\n        var x = this.getInputData(0);\n        if (x == null) {\n            x = this.properties.x;\n        }\n        var y = this.getInputData(1);\n        if (y == null) {\n            y = this.properties.y;\n        }\n        var z = this.getInputData(2);\n        if (z == null) {\n            z = this.properties.z;\n        }\n        var w = this.getInputData(3);\n        if (w == null) {\n            w = this.properties.w;\n        }\n\n        var data = this._data;\n        data[0] = x;\n        data[1] = y;\n        data[2] = z;\n        data[3] = w;\n\n        this.setOutputData(0, data);\n    };\n\n    LiteGraph.registerNodeType(\"math3d/xyzw-to-vec4\", Math3DXYZWToVec4);\n\n})(this);\n"
  },
  {
    "path": "src/nodes/math3d.js",
    "content": "(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n\r\n\tfunction Math3DMat4()\r\n\t{\r\n        this.addInput(\"T\", \"vec3\");\r\n        this.addInput(\"R\", \"vec3\");\r\n        this.addInput(\"S\", \"vec3\");\r\n        this.addOutput(\"mat4\", \"mat4\");\r\n\t\tthis.properties = {\r\n\t\t\t\"T\":[0,0,0],\r\n\t\t\t\"R\":[0,0,0],\r\n\t\t\t\"S\":[1,1,1],\r\n\t\t\tR_in_degrees: true\r\n\t\t};\r\n\t\tthis._result = mat4.create();\r\n\t\tthis._must_update = true;\r\n\t}\r\n\r\n\tMath3DMat4.title = \"mat4\";\r\n\tMath3DMat4.temp_quat = new Float32Array([0,0,0,1]);\r\n\tMath3DMat4.temp_mat4 = new Float32Array(16);\r\n\tMath3DMat4.temp_vec3 = new Float32Array(3);\r\n\r\n\tMath3DMat4.prototype.onPropertyChanged = function(name, value)\r\n\t{\r\n\t\tthis._must_update = true;\r\n\t}\r\n\r\n\tMath3DMat4.prototype.onExecute = function()\r\n\t{\r\n\t\tvar M = this._result;\r\n\t\tvar Q = Math3DMat4.temp_quat;\r\n\t\tvar temp_mat4 = Math3DMat4.temp_mat4;\r\n\t\tvar temp_vec3 = Math3DMat4.temp_vec3;\r\n\r\n\t\tvar T = this.getInputData(0);\r\n\t\tvar R = this.getInputData(1);\r\n\t\tvar S = this.getInputData(2);\r\n\r\n\t\tif( this._must_update || T || R || S )\r\n\t\t{\r\n\t\t\tT = T || this.properties.T;\r\n\t\t\tR = R || this.properties.R;\r\n\t\t\tS = S || this.properties.S;\r\n\t\t\tmat4.identity( M );\r\n\t\t\tmat4.translate( M, M, T );\r\n\t\t\tif(this.properties.R_in_degrees)\r\n\t\t\t{\r\n\t\t\t\ttemp_vec3.set( R );\r\n\t\t\t\tvec3.scale(temp_vec3,temp_vec3,DEG2RAD);\r\n\t\t\t\tquat.fromEuler( Q, temp_vec3 );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t\tquat.fromEuler( Q, R );\r\n\t\t\tmat4.fromQuat( temp_mat4, Q );\r\n\t\t\tmat4.multiply( M, M, temp_mat4 );\r\n\t\t\tmat4.scale( M, M, S );\r\n\t\t}\r\n\r\n\t\tthis.setOutputData(0, M);\t\t\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"math3d/mat4\", Math3DMat4);\r\n\r\n    //Math 3D operation\r\n    function Math3DOperation() {\r\n        this.addInput(\"A\", \"number,vec3\");\r\n        this.addInput(\"B\", \"number,vec3\");\r\n        this.addOutput(\"=\", \"number,vec3\");\r\n        this.addProperty(\"OP\", \"+\", \"enum\", { values: Math3DOperation.values });\r\n\t\tthis._result = vec3.create();\r\n    }\r\n\r\n    Math3DOperation.values = [\"+\", \"-\", \"*\", \"/\", \"%\", \"^\", \"max\", \"min\",\"dot\",\"cross\"];\r\n\r\n    LiteGraph.registerSearchboxExtra(\"math3d/operation\", \"CROSS()\", {\r\n        properties: {\"OP\":\"cross\"},\r\n        title: \"CROSS()\"\r\n    });\r\n\r\n    LiteGraph.registerSearchboxExtra(\"math3d/operation\", \"DOT()\", {\r\n        properties: {\"OP\":\"dot\"},\r\n        title: \"DOT()\"\r\n    });\r\n\r\n\tMath3DOperation.title = \"Operation\";\r\n    Math3DOperation.desc = \"Easy math 3D operators\";\r\n    Math3DOperation[\"@OP\"] = {\r\n        type: \"enum\",\r\n        title: \"operation\",\r\n        values: Math3DOperation.values\r\n    };\r\n    Math3DOperation.size = [100, 60];\r\n\r\n    Math3DOperation.prototype.getTitle = function() {\r\n\t\tif(this.properties.OP == \"max\" || this.properties.OP == \"min\" )\r\n\t\t\treturn this.properties.OP + \"(A,B)\";\r\n        return \"A \" + this.properties.OP + \" B\";\r\n    };\r\n\r\n    Math3DOperation.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        var B = this.getInputData(1);\r\n\t\tif(A == null || B == null)\r\n\t\t\treturn;\r\n\t\tif(A.constructor === Number)\r\n\t\t\tA = [A,A,A];\r\n\t\tif(B.constructor === Number)\r\n\t\t\tB = [B,B,B];\r\n\r\n        var result = this._result;\r\n        switch (this.properties.OP) {\r\n            case \"+\":\r\n                result = vec3.add(result,A,B);\r\n                break;\r\n            case \"-\":\r\n                result = vec3.sub(result,A,B);\r\n                break;\r\n            case \"x\":\r\n            case \"X\":\r\n            case \"*\":\r\n                result = vec3.mul(result,A,B);\r\n                break;\r\n            case \"/\":\r\n                result = vec3.div(result,A,B);\r\n                break;\r\n            case \"%\":\r\n                result[0] = A[0]%B[0];\r\n                result[1] = A[1]%B[1];\r\n                result[2] = A[2]%B[2];\r\n                break;\r\n            case \"^\":\r\n                result[0] = Math.pow(A[0],B[0]);\r\n                result[1] = Math.pow(A[1],B[1]);\r\n                result[2] = Math.pow(A[2],B[2]);\r\n                break;\r\n            case \"max\":\r\n                result[0] = Math.max(A[0],B[0]);\r\n                result[1] = Math.max(A[1],B[1]);\r\n                result[2] = Math.max(A[2],B[2]);\r\n                break;\r\n            case \"min\":\r\n                result[0] = Math.min(A[0],B[0]);\r\n                result[1] = Math.min(A[1],B[1]);\r\n                result[2] = Math.min(A[2],B[2]);\r\n            case \"dot\":\r\n                result = vec3.dot(A,B);\r\n                break;\r\n            case \"cross\":\r\n                vec3.cross(result,A,B);\r\n                break;\r\n            default:\r\n                console.warn(\"Unknown operation: \" + this.properties.OP);\r\n        }\r\n        this.setOutputData(0, result);\r\n    };\r\n\r\n    Math3DOperation.prototype.onDrawBackground = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        ctx.font = \"40px Arial\";\r\n        ctx.fillStyle = \"#666\";\r\n        ctx.textAlign = \"center\";\r\n        ctx.fillText(\r\n            this.properties.OP,\r\n            this.size[0] * 0.5,\r\n            (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5\r\n        );\r\n        ctx.textAlign = \"left\";\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/operation\", Math3DOperation);\r\n\r\n    function Math3DVec3Scale() {\r\n        this.addInput(\"in\", \"vec3\");\r\n        this.addInput(\"f\", \"number\");\r\n        this.addOutput(\"out\", \"vec3\");\r\n        this.properties = { f: 1 };\r\n        this._data = new Float32Array(3);\r\n    }\r\n\r\n    Math3DVec3Scale.title = \"vec3_scale\";\r\n    Math3DVec3Scale.desc = \"scales the components of a vec3\";\r\n\r\n    Math3DVec3Scale.prototype.onExecute = function() {\r\n        var v = this.getInputData(0);\r\n        if (v == null) {\r\n            return;\r\n        }\r\n        var f = this.getInputData(1);\r\n        if (f == null) {\r\n            f = this.properties.f;\r\n        }\r\n\r\n        var data = this._data;\r\n        data[0] = v[0] * f;\r\n        data[1] = v[1] * f;\r\n        data[2] = v[2] * f;\r\n        this.setOutputData(0, data);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/vec3-scale\", Math3DVec3Scale);\r\n\r\n    function Math3DVec3Length() {\r\n        this.addInput(\"in\", \"vec3\");\r\n        this.addOutput(\"out\", \"number\");\r\n    }\r\n\r\n    Math3DVec3Length.title = \"vec3_length\";\r\n    Math3DVec3Length.desc = \"returns the module of a vector\";\r\n\r\n    Math3DVec3Length.prototype.onExecute = function() {\r\n        var v = this.getInputData(0);\r\n        if (v == null) {\r\n            return;\r\n        }\r\n        var dist = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);\r\n        this.setOutputData(0, dist);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/vec3-length\", Math3DVec3Length);\r\n\r\n    function Math3DVec3Normalize() {\r\n        this.addInput(\"in\", \"vec3\");\r\n        this.addOutput(\"out\", \"vec3\");\r\n        this._data = new Float32Array(3);\r\n    }\r\n\r\n    Math3DVec3Normalize.title = \"vec3_normalize\";\r\n    Math3DVec3Normalize.desc = \"returns the vector normalized\";\r\n\r\n    Math3DVec3Normalize.prototype.onExecute = function() {\r\n        var v = this.getInputData(0);\r\n        if (v == null) {\r\n            return;\r\n        }\r\n        var dist = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);\r\n        var data = this._data;\r\n        data[0] = v[0] / dist;\r\n        data[1] = v[1] / dist;\r\n        data[2] = v[2] / dist;\r\n\r\n        this.setOutputData(0, data);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/vec3-normalize\", Math3DVec3Normalize);\r\n\r\n    function Math3DVec3Lerp() {\r\n        this.addInput(\"A\", \"vec3\");\r\n        this.addInput(\"B\", \"vec3\");\r\n        this.addInput(\"f\", \"vec3\");\r\n        this.addOutput(\"out\", \"vec3\");\r\n        this.properties = { f: 0.5 };\r\n        this._data = new Float32Array(3);\r\n    }\r\n\r\n    Math3DVec3Lerp.title = \"vec3_lerp\";\r\n    Math3DVec3Lerp.desc = \"returns the interpolated vector\";\r\n\r\n    Math3DVec3Lerp.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        if (A == null) {\r\n            return;\r\n        }\r\n        var B = this.getInputData(1);\r\n        if (B == null) {\r\n            return;\r\n        }\r\n        var f = this.getInputOrProperty(\"f\");\r\n\r\n        var data = this._data;\r\n        data[0] = A[0] * (1 - f) + B[0] * f;\r\n        data[1] = A[1] * (1 - f) + B[1] * f;\r\n        data[2] = A[2] * (1 - f) + B[2] * f;\r\n\r\n        this.setOutputData(0, data);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/vec3-lerp\", Math3DVec3Lerp);\r\n\r\n    function Math3DVec3Dot() {\r\n        this.addInput(\"A\", \"vec3\");\r\n        this.addInput(\"B\", \"vec3\");\r\n        this.addOutput(\"out\", \"number\");\r\n    }\r\n\r\n    Math3DVec3Dot.title = \"vec3_dot\";\r\n    Math3DVec3Dot.desc = \"returns the dot product\";\r\n\r\n    Math3DVec3Dot.prototype.onExecute = function() {\r\n        var A = this.getInputData(0);\r\n        if (A == null) {\r\n            return;\r\n        }\r\n        var B = this.getInputData(1);\r\n        if (B == null) {\r\n            return;\r\n        }\r\n\r\n        var dot = A[0] * B[0] + A[1] * B[1] + A[2] * B[2];\r\n        this.setOutputData(0, dot);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"math3d/vec3-dot\", Math3DVec3Dot);\r\n\r\n    //if glMatrix is installed...\r\n    if (global.glMatrix) {\r\n        function Math3DQuaternion() {\r\n            this.addOutput(\"quat\", \"quat\");\r\n            this.properties = { x: 0, y: 0, z: 0, w: 1, normalize: false };\r\n            this._value = quat.create();\r\n        }\r\n\r\n        Math3DQuaternion.title = \"Quaternion\";\r\n        Math3DQuaternion.desc = \"quaternion\";\r\n\r\n        Math3DQuaternion.prototype.onExecute = function() {\r\n            this._value[0] = this.getInputOrProperty(\"x\");\r\n            this._value[1] = this.getInputOrProperty(\"y\");\r\n            this._value[2] = this.getInputOrProperty(\"z\");\r\n            this._value[3] = this.getInputOrProperty(\"w\");\r\n            if (this.properties.normalize) {\r\n                quat.normalize(this._value, this._value);\r\n            }\r\n            this.setOutputData(0, this._value);\r\n        };\r\n\r\n        Math3DQuaternion.prototype.onGetInputs = function() {\r\n            return [\r\n                [\"x\", \"number\"],\r\n                [\"y\", \"number\"],\r\n                [\"z\", \"number\"],\r\n                [\"w\", \"number\"]\r\n            ];\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/quaternion\", Math3DQuaternion);\r\n\r\n        function Math3DRotation() {\r\n            this.addInputs([[\"degrees\", \"number\"], [\"axis\", \"vec3\"]]);\r\n            this.addOutput(\"quat\", \"quat\");\r\n            this.properties = { angle: 90.0, axis: vec3.fromValues(0, 1, 0) };\r\n\r\n            this._value = quat.create();\r\n        }\r\n\r\n        Math3DRotation.title = \"Rotation\";\r\n        Math3DRotation.desc = \"quaternion rotation\";\r\n\r\n        Math3DRotation.prototype.onExecute = function() {\r\n            var angle = this.getInputData(0);\r\n            if (angle == null) {\r\n                angle = this.properties.angle;\r\n            }\r\n            var axis = this.getInputData(1);\r\n            if (axis == null) {\r\n                axis = this.properties.axis;\r\n            }\r\n\r\n            var R = quat.setAxisAngle(this._value, axis, angle * 0.0174532925);\r\n            this.setOutputData(0, R);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/rotation\", Math3DRotation);\r\n\r\n\r\n        function MathEulerToQuat() {\r\n            this.addInput(\"euler\", \"vec3\");\r\n            this.addOutput(\"quat\", \"quat\");\r\n            this.properties = { euler:[0,0,0], use_yaw_pitch_roll: false };\r\n\t\t\tthis._degs = vec3.create();\r\n            this._value = quat.create();\r\n        }\r\n\r\n        MathEulerToQuat.title = \"Euler->Quat\";\r\n        MathEulerToQuat.desc = \"Converts euler angles (in degrees) to quaternion\";\r\n\r\n        MathEulerToQuat.prototype.onExecute = function() {\r\n            var euler = this.getInputData(0);\r\n            if (euler == null) {\r\n                euler = this.properties.euler;\r\n            }\r\n\t\t\tvec3.scale( this._degs, euler, DEG2RAD );\r\n\t\t\tif(this.properties.use_yaw_pitch_roll)\r\n\t\t\t\tthis._degs = [this._degs[2],this._degs[0],this._degs[1]];\r\n            var R = quat.fromEuler(this._value, this._degs);\r\n            this.setOutputData(0, R);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/euler_to_quat\", MathEulerToQuat);\r\n\r\n        function MathQuatToEuler() {\r\n            this.addInput([\"quat\", \"quat\"]);\r\n            this.addOutput(\"euler\", \"vec3\");\r\n\t\t\tthis._value = vec3.create();\r\n        }\r\n\r\n        MathQuatToEuler.title = \"Euler->Quat\";\r\n        MathQuatToEuler.desc = \"Converts rotX,rotY,rotZ in degrees to quat\";\r\n\r\n        MathQuatToEuler.prototype.onExecute = function() {\r\n            var q = this.getInputData(0);\r\n\t\t\tif(!q)\r\n\t\t\t\treturn;\r\n            var R = quat.toEuler(this._value, q);\r\n\t\t\tvec3.scale( this._value, this._value, DEG2RAD );\r\n            this.setOutputData(0, this._value);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/quat_to_euler\", MathQuatToEuler);\r\n\r\n\r\n        //Math3D rotate vec3\r\n        function Math3DRotateVec3() {\r\n            this.addInputs([[\"vec3\", \"vec3\"], [\"quat\", \"quat\"]]);\r\n            this.addOutput(\"result\", \"vec3\");\r\n            this.properties = { vec: [0, 0, 1] };\r\n        }\r\n\r\n        Math3DRotateVec3.title = \"Rot. Vec3\";\r\n        Math3DRotateVec3.desc = \"rotate a point\";\r\n\r\n        Math3DRotateVec3.prototype.onExecute = function() {\r\n            var vec = this.getInputData(0);\r\n            if (vec == null) {\r\n                vec = this.properties.vec;\r\n            }\r\n            var quat = this.getInputData(1);\r\n            if (quat == null) {\r\n                this.setOutputData(vec);\r\n            } else {\r\n                this.setOutputData(\r\n                    0,\r\n                    vec3.transformQuat(vec3.create(), vec, quat)\r\n                );\r\n            }\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/rotate_vec3\", Math3DRotateVec3);\r\n\r\n        function Math3DMultQuat() {\r\n            this.addInputs([[\"A\", \"quat\"], [\"B\", \"quat\"]]);\r\n            this.addOutput(\"A*B\", \"quat\");\r\n\r\n            this._value = quat.create();\r\n        }\r\n\r\n        Math3DMultQuat.title = \"Mult. Quat\";\r\n        Math3DMultQuat.desc = \"rotate quaternion\";\r\n\r\n        Math3DMultQuat.prototype.onExecute = function() {\r\n            var A = this.getInputData(0);\r\n            if (A == null) {\r\n                return;\r\n            }\r\n            var B = this.getInputData(1);\r\n            if (B == null) {\r\n                return;\r\n            }\r\n\r\n            var R = quat.multiply(this._value, A, B);\r\n            this.setOutputData(0, R);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/mult-quat\", Math3DMultQuat);\r\n\r\n        function Math3DQuatSlerp() {\r\n            this.addInputs([\r\n                [\"A\", \"quat\"],\r\n                [\"B\", \"quat\"],\r\n                [\"factor\", \"number\"]\r\n            ]);\r\n            this.addOutput(\"slerp\", \"quat\");\r\n            this.addProperty(\"factor\", 0.5);\r\n\r\n            this._value = quat.create();\r\n        }\r\n\r\n        Math3DQuatSlerp.title = \"Quat Slerp\";\r\n        Math3DQuatSlerp.desc = \"quaternion spherical interpolation\";\r\n\r\n        Math3DQuatSlerp.prototype.onExecute = function() {\r\n            var A = this.getInputData(0);\r\n            if (A == null) {\r\n                return;\r\n            }\r\n            var B = this.getInputData(1);\r\n            if (B == null) {\r\n                return;\r\n            }\r\n            var factor = this.properties.factor;\r\n            if (this.getInputData(2) != null) {\r\n                factor = this.getInputData(2);\r\n            }\r\n\r\n            var R = quat.slerp(this._value, A, B, factor);\r\n            this.setOutputData(0, R);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/quat-slerp\", Math3DQuatSlerp);\r\n\r\n\r\n        //Math3D rotate vec3\r\n        function Math3DRemapRange() {\r\n            this.addInput(\"vec3\", \"vec3\");\r\n            this.addOutput(\"remap\", \"vec3\");\r\n\t\t\tthis.addOutput(\"clamped\", \"vec3\");\r\n            this.properties = { clamp: true, range_min: [-1, -1, 0], range_max: [1, 1, 0], target_min: [-1,-1,0], target_max:[1,1,0] };\r\n\t\t\tthis._value = vec3.create();\r\n\t\t\tthis._clamped = vec3.create();\r\n        }\r\n\r\n        Math3DRemapRange.title = \"Remap Range\";\r\n        Math3DRemapRange.desc = \"remap a 3D range\";\r\n\r\n        Math3DRemapRange.prototype.onExecute = function() {\r\n            var vec = this.getInputData(0);\r\n\t\t\tif(vec)\r\n\t\t\t\tthis._value.set(vec);\r\n\t\t\tvar range_min = this.properties.range_min;\r\n\t\t\tvar range_max = this.properties.range_max;\r\n\t\t\tvar target_min = this.properties.target_min;\r\n\t\t\tvar target_max = this.properties.target_max;\r\n\r\n\t\t\t//swap to avoid errors\r\n\t\t\t/*\r\n\t\t\tif(range_min > range_max)\r\n\t\t\t{\r\n\t\t\t\trange_min = range_max;\r\n\t\t\t\trange_max = this.properties.range_min;\r\n\t\t\t}\r\n\r\n\t\t\tif(target_min > target_max)\r\n\t\t\t{\r\n\t\t\t\ttarget_min = target_max;\r\n\t\t\t\ttarget_max = this.properties.target_min;\r\n\t\t\t}\r\n\t\t\t*/\r\n\r\n\t\t\tfor(var i = 0; i < 3; ++i)\r\n\t\t\t{\r\n\t\t\t\tvar r = range_max[i] - range_min[i];\r\n\t\t\t\tthis._clamped[i] = clamp( this._value[i], range_min[i], range_max[i] );\r\n\t\t\t\tif(r == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis._value[i] = (target_min[i] + target_max[i]) * 0.5;\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar n = (this._value[i] - range_min[i]) / r;\r\n\t\t\t\tif(this.properties.clamp)\r\n\t\t\t\t\tn = clamp(n,0,1);\r\n\t\t\t\tvar t = target_max[i] - target_min[i];\r\n\t\t\t\tthis._value[i] = target_min[i] + n * t;\r\n\t\t\t}\r\n\r\n\t\t\tthis.setOutputData(0,this._value);\r\n\t\t\tthis.setOutputData(1,this._clamped);\r\n        };\r\n\r\n        LiteGraph.registerNodeType(\"math3d/remap_range\", Math3DRemapRange);\r\n\r\n\r\n\r\n    } //glMatrix\r\n\telse if (LiteGraph.debug)\r\n\t\tconsole.warn(\"No glmatrix found, some Math3D nodes may not work\");\r\n\r\n})(this);\r\n"
  },
  {
    "path": "src/nodes/midi.js",
    "content": "(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n    var MIDI_COLOR = \"#243\";\r\n\r\n    function MIDIEvent(data) {\r\n        this.channel = 0;\r\n        this.cmd = 0;\r\n        this.data = new Uint32Array(3);\r\n\r\n        if (data) {\r\n            this.setup(data);\r\n        }\r\n    }\r\n\r\n    LiteGraph.MIDIEvent = MIDIEvent;\r\n\r\n    MIDIEvent.prototype.fromJSON = function(o) {\r\n        this.setup(o.data);\r\n    };\r\n\r\n    MIDIEvent.prototype.setup = function(data) {\r\n        var raw_data = data;\r\n        if (data.constructor === Object) {\r\n            raw_data = data.data;\r\n        }\r\n\r\n        this.data.set(raw_data);\r\n\r\n        var midiStatus = raw_data[0];\r\n        this.status = midiStatus;\r\n\r\n        var midiCommand = midiStatus & 0xf0;\r\n\r\n        if (midiStatus >= 0xf0) {\r\n            this.cmd = midiStatus;\r\n        } else {\r\n            this.cmd = midiCommand;\r\n        }\r\n\r\n        if (this.cmd == MIDIEvent.NOTEON && this.velocity == 0) {\r\n            this.cmd = MIDIEvent.NOTEOFF;\r\n        }\r\n\r\n        this.cmd_str = MIDIEvent.commands[this.cmd] || \"\";\r\n\r\n        if (\r\n            midiCommand >= MIDIEvent.NOTEON ||\r\n            midiCommand <= MIDIEvent.NOTEOFF\r\n        ) {\r\n            this.channel = midiStatus & 0x0f;\r\n        }\r\n    };\r\n\r\n    Object.defineProperty(MIDIEvent.prototype, \"velocity\", {\r\n        get: function() {\r\n            if (this.cmd == MIDIEvent.NOTEON) {\r\n                return this.data[2];\r\n            }\r\n            return -1;\r\n        },\r\n        set: function(v) {\r\n            this.data[2] = v; //  v / 127;\r\n        },\r\n        enumerable: true\r\n    });\r\n\r\n    MIDIEvent.notes = [\r\n        \"A\",\r\n        \"A#\",\r\n        \"B\",\r\n        \"C\",\r\n        \"C#\",\r\n        \"D\",\r\n        \"D#\",\r\n        \"E\",\r\n        \"F\",\r\n        \"F#\",\r\n        \"G\",\r\n        \"G#\"\r\n    ];\r\n    MIDIEvent.note_to_index = {\r\n        A: 0,\r\n        \"A#\": 1,\r\n        B: 2,\r\n        C: 3,\r\n        \"C#\": 4,\r\n        D: 5,\r\n        \"D#\": 6,\r\n        E: 7,\r\n        F: 8,\r\n        \"F#\": 9,\r\n        G: 10,\r\n        \"G#\": 11\r\n    };\r\n\r\n    Object.defineProperty(MIDIEvent.prototype, \"note\", {\r\n        get: function() {\r\n            if (this.cmd != MIDIEvent.NOTEON) {\r\n                return -1;\r\n            }\r\n            return MIDIEvent.toNoteString(this.data[1], true);\r\n        },\r\n        set: function(v) {\r\n            throw \"notes cannot be assigned this way, must modify the data[1]\";\r\n        },\r\n        enumerable: true\r\n    });\r\n\r\n    Object.defineProperty(MIDIEvent.prototype, \"octave\", {\r\n        get: function() {\r\n            if (this.cmd != MIDIEvent.NOTEON) {\r\n                return -1;\r\n            }\r\n            var octave = this.data[1] - 24;\r\n            return Math.floor(octave / 12 + 1);\r\n        },\r\n        set: function(v) {\r\n            throw \"octave cannot be assigned this way, must modify the data[1]\";\r\n        },\r\n        enumerable: true\r\n    });\r\n\r\n    //returns HZs\r\n    MIDIEvent.prototype.getPitch = function() {\r\n        return Math.pow(2, (this.data[1] - 69) / 12) * 440;\r\n    };\r\n\r\n    MIDIEvent.computePitch = function(note) {\r\n        return Math.pow(2, (note - 69) / 12) * 440;\r\n    };\r\n\r\n    MIDIEvent.prototype.getCC = function() {\r\n        return this.data[1];\r\n    };\r\n\r\n    MIDIEvent.prototype.getCCValue = function() {\r\n        return this.data[2];\r\n    };\r\n\r\n    //not tested, there is a formula missing here\r\n    MIDIEvent.prototype.getPitchBend = function() {\r\n        return this.data[1] + (this.data[2] << 7) - 8192;\r\n    };\r\n\r\n    MIDIEvent.computePitchBend = function(v1, v2) {\r\n        return v1 + (v2 << 7) - 8192;\r\n    };\r\n\r\n    MIDIEvent.prototype.setCommandFromString = function(str) {\r\n        this.cmd = MIDIEvent.computeCommandFromString(str);\r\n    };\r\n\r\n    MIDIEvent.computeCommandFromString = function(str) {\r\n        if (!str) {\r\n            return 0;\r\n        }\r\n\r\n        if (str && str.constructor === Number) {\r\n            return str;\r\n        }\r\n\r\n        str = str.toUpperCase();\r\n        switch (str) {\r\n            case \"NOTE ON\":\r\n            case \"NOTEON\":\r\n                return MIDIEvent.NOTEON;\r\n                break;\r\n            case \"NOTE OFF\":\r\n            case \"NOTEOFF\":\r\n                return MIDIEvent.NOTEON;\r\n                break;\r\n            case \"KEY PRESSURE\":\r\n            case \"KEYPRESSURE\":\r\n                return MIDIEvent.KEYPRESSURE;\r\n                break;\r\n            case \"CONTROLLER CHANGE\":\r\n            case \"CONTROLLERCHANGE\":\r\n            case \"CC\":\r\n                return MIDIEvent.CONTROLLERCHANGE;\r\n                break;\r\n            case \"PROGRAM CHANGE\":\r\n            case \"PROGRAMCHANGE\":\r\n            case \"PC\":\r\n                return MIDIEvent.PROGRAMCHANGE;\r\n                break;\r\n            case \"CHANNEL PRESSURE\":\r\n            case \"CHANNELPRESSURE\":\r\n                return MIDIEvent.CHANNELPRESSURE;\r\n                break;\r\n            case \"PITCH BEND\":\r\n            case \"PITCHBEND\":\r\n                return MIDIEvent.PITCHBEND;\r\n                break;\r\n            case \"TIME TICK\":\r\n            case \"TIMETICK\":\r\n                return MIDIEvent.TIMETICK;\r\n                break;\r\n            default:\r\n                return Number(str); //assume its a hex code\r\n        }\r\n    };\r\n\r\n    //transform from a pitch number to string like \"C4\"\r\n    MIDIEvent.toNoteString = function(d, skip_octave) {\r\n        d = Math.round(d); //in case it has decimals\r\n        var note = d - 21;\r\n        var octave = Math.floor((d - 24) / 12 + 1);\r\n        note = note % 12;\r\n        if (note < 0) {\r\n            note = 12 + note;\r\n        }\r\n        return MIDIEvent.notes[note] + (skip_octave ? \"\" : octave);\r\n    };\r\n\r\n    MIDIEvent.NoteStringToPitch = function(str) {\r\n        str = str.toUpperCase();\r\n        var note = str[0];\r\n        var octave = 4;\r\n\r\n        if (str[1] == \"#\") {\r\n            note += \"#\";\r\n            if (str.length > 2) {\r\n                octave = Number(str[2]);\r\n            }\r\n        } else {\r\n            if (str.length > 1) {\r\n                octave = Number(str[1]);\r\n            }\r\n        }\r\n        var pitch = MIDIEvent.note_to_index[note];\r\n        if (pitch == null) {\r\n            return null;\r\n        }\r\n        return (octave - 1) * 12 + pitch + 21;\r\n    };\r\n\r\n    MIDIEvent.prototype.toString = function() {\r\n        var str = \"\" + this.channel + \". \";\r\n        switch (this.cmd) {\r\n            case MIDIEvent.NOTEON:\r\n                str += \"NOTEON \" + MIDIEvent.toNoteString(this.data[1]);\r\n                break;\r\n            case MIDIEvent.NOTEOFF:\r\n                str += \"NOTEOFF \" + MIDIEvent.toNoteString(this.data[1]);\r\n                break;\r\n            case MIDIEvent.CONTROLLERCHANGE:\r\n                str += \"CC \" + this.data[1] + \" \" + this.data[2];\r\n                break;\r\n            case MIDIEvent.PROGRAMCHANGE:\r\n                str += \"PC \" + this.data[1];\r\n                break;\r\n            case MIDIEvent.PITCHBEND:\r\n                str += \"PITCHBEND \" + this.getPitchBend();\r\n                break;\r\n            case MIDIEvent.KEYPRESSURE:\r\n                str += \"KEYPRESS \" + this.data[1];\r\n                break;\r\n        }\r\n\r\n        return str;\r\n    };\r\n\r\n    MIDIEvent.prototype.toHexString = function() {\r\n        var str = \"\";\r\n        for (var i = 0; i < this.data.length; i++) {\r\n            str += this.data[i].toString(16) + \" \";\r\n        }\r\n    };\r\n\r\n    MIDIEvent.prototype.toJSON = function() {\r\n        return {\r\n            data: [this.data[0], this.data[1], this.data[2]],\r\n            object_class: \"MIDIEvent\"\r\n        };\r\n    };\r\n\r\n    MIDIEvent.NOTEOFF = 0x80;\r\n    MIDIEvent.NOTEON = 0x90;\r\n    MIDIEvent.KEYPRESSURE = 0xa0;\r\n    MIDIEvent.CONTROLLERCHANGE = 0xb0;\r\n    MIDIEvent.PROGRAMCHANGE = 0xc0;\r\n    MIDIEvent.CHANNELPRESSURE = 0xd0;\r\n    MIDIEvent.PITCHBEND = 0xe0;\r\n    MIDIEvent.TIMETICK = 0xf8;\r\n\r\n    MIDIEvent.commands = {\r\n        0x80: \"note off\",\r\n        0x90: \"note on\",\r\n        0xa0: \"key pressure\",\r\n        0xb0: \"controller change\",\r\n        0xc0: \"program change\",\r\n        0xd0: \"channel pressure\",\r\n        0xe0: \"pitch bend\",\r\n        0xf0: \"system\",\r\n        0xf2: \"Song pos\",\r\n        0xf3: \"Song select\",\r\n        0xf6: \"Tune request\",\r\n        0xf8: \"time tick\",\r\n        0xfa: \"Start Song\",\r\n        0xfb: \"Continue Song\",\r\n        0xfc: \"Stop Song\",\r\n        0xfe: \"Sensing\",\r\n        0xff: \"Reset\"\r\n    };\r\n\r\n    MIDIEvent.commands_short = {\r\n        0x80: \"NOTEOFF\",\r\n        0x90: \"NOTEOFF\",\r\n        0xa0: \"KEYP\",\r\n        0xb0: \"CC\",\r\n        0xc0: \"PC\",\r\n        0xd0: \"CP\",\r\n        0xe0: \"PB\",\r\n        0xf0: \"SYS\",\r\n        0xf2: \"POS\",\r\n        0xf3: \"SELECT\",\r\n        0xf6: \"TUNEREQ\",\r\n        0xf8: \"TT\",\r\n        0xfa: \"START\",\r\n        0xfb: \"CONTINUE\",\r\n        0xfc: \"STOP\",\r\n        0xfe: \"SENS\",\r\n        0xff: \"RESET\"\r\n    };\r\n\r\n    MIDIEvent.commands_reversed = {};\r\n    for (var i in MIDIEvent.commands) {\r\n        MIDIEvent.commands_reversed[MIDIEvent.commands[i]] = i;\r\n    }\r\n\r\n    //MIDI wrapper, instantiate by MIDIIn and MIDIOut\r\n    function MIDIInterface(on_ready, on_error) {\r\n        if (!navigator.requestMIDIAccess) {\r\n            this.error = \"not suppoorted\";\r\n            if (on_error) {\r\n                on_error(\"Not supported\");\r\n            } else {\r\n                console.error(\"MIDI NOT SUPPORTED, enable by chrome://flags\");\r\n            }\r\n            return;\r\n        }\r\n\r\n        this.on_ready = on_ready;\r\n\r\n        this.state = {\r\n            note: [],\r\n            cc: []\r\n        };\r\n\r\n\t\tthis.input_ports = null;\r\n\t\tthis.input_ports_info = [];\r\n\t\tthis.output_ports = null;\r\n\t\tthis.output_ports_info = [];\r\n\r\n        navigator.requestMIDIAccess().then(this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this));\r\n    }\r\n\r\n    MIDIInterface.input = null;\r\n\r\n    MIDIInterface.MIDIEvent = MIDIEvent;\r\n\r\n    MIDIInterface.prototype.onMIDISuccess = function(midiAccess) {\r\n        console.log(\"MIDI ready!\");\r\n        console.log(midiAccess);\r\n        this.midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance)\r\n        this.updatePorts();\r\n\r\n        if (this.on_ready) {\r\n            this.on_ready(this);\r\n        }\r\n    };\r\n\r\n    MIDIInterface.prototype.updatePorts = function() {\r\n        var midi = this.midi;\r\n        this.input_ports = midi.inputs;\r\n\t\tthis.input_ports_info = [];\r\n        this.output_ports = midi.outputs;\r\n\t\tthis.output_ports_info = [];\r\n\r\n        var num = 0;\r\n\r\n        var it = this.input_ports.values();\r\n        var it_value = it.next();\r\n        while (it_value && it_value.done === false) {\r\n            var port_info = it_value.value;\r\n\t\t\tthis.input_ports_info.push(port_info);\r\n            console.log( \"Input port [type:'\" + port_info.type + \"'] id:'\" + port_info.id + \"' manufacturer:'\" + port_info.manufacturer + \"' name:'\" + port_info.name + \"' version:'\" + port_info.version + \"'\" );\r\n            num++;\r\n            it_value = it.next();\r\n        }\r\n        this.num_input_ports = num;\r\n\r\n        num = 0;\r\n        var it = this.output_ports.values();\r\n        var it_value = it.next();\r\n        while (it_value && it_value.done === false) {\r\n            var port_info = it_value.value;\r\n\t\t\tthis.output_ports_info.push(port_info);\r\n            console.log( \"Output port [type:'\" + port_info.type + \"'] id:'\" + port_info.id + \"' manufacturer:'\" + port_info.manufacturer + \"' name:'\" + port_info.name + \"' version:'\" + port_info.version + \"'\" );\r\n            num++;\r\n            it_value = it.next();\r\n        }\r\n        this.num_output_ports = num;\r\n    };\r\n\r\n    MIDIInterface.prototype.onMIDIFailure = function(msg) {\r\n        console.error(\"Failed to get MIDI access - \" + msg);\r\n    };\r\n\r\n    MIDIInterface.prototype.openInputPort = function(port, callback) {\r\n        var input_port = this.input_ports.get(\"input-\" + port);\r\n        if (!input_port) {\r\n            return false;\r\n        }\r\n        MIDIInterface.input = this;\r\n        var that = this;\r\n\r\n        input_port.onmidimessage = function(a) {\r\n            var midi_event = new MIDIEvent(a.data);\r\n            that.updateState(midi_event);\r\n            if (callback) {\r\n                callback(a.data, midi_event);\r\n            }\r\n            if (MIDIInterface.on_message) {\r\n                MIDIInterface.on_message(a.data, midi_event);\r\n            }\r\n        };\r\n        console.log(\"port open: \", input_port);\r\n        return true;\r\n    };\r\n\r\n    MIDIInterface.parseMsg = function(data) {};\r\n\r\n    MIDIInterface.prototype.updateState = function(midi_event) {\r\n        switch (midi_event.cmd) {\r\n            case MIDIEvent.NOTEON:\r\n                this.state.note[midi_event.value1 | 0] = midi_event.value2;\r\n                break;\r\n            case MIDIEvent.NOTEOFF:\r\n                this.state.note[midi_event.value1 | 0] = 0;\r\n                break;\r\n            case MIDIEvent.CONTROLLERCHANGE:\r\n                this.state.cc[midi_event.getCC()] = midi_event.getCCValue();\r\n                break;\r\n        }\r\n    };\r\n\r\n    MIDIInterface.prototype.sendMIDI = function(port, midi_data) {\r\n        if (!midi_data) {\r\n            return;\r\n        }\r\n\r\n        var output_port = this.output_ports_info[port];//this.output_ports.get(\"output-\" + port);\r\n        if (!output_port) {\r\n            return;\r\n        }\r\n\r\n        MIDIInterface.output = this;\r\n\r\n        if (midi_data.constructor === MIDIEvent) {\r\n            output_port.send(midi_data.data);\r\n        } else {\r\n            output_port.send(midi_data);\r\n        }\r\n    };\r\n\r\n    function LGMIDIIn() {\r\n        this.addOutput(\"on_midi\", LiteGraph.EVENT);\r\n        this.addOutput(\"out\", \"midi\");\r\n        this.properties = { port: 0 };\r\n        this._last_midi_event = null;\r\n        this._current_midi_event = null;\r\n        this.boxcolor = \"#AAA\";\r\n        this._last_time = 0;\r\n\r\n        var that = this;\r\n        new MIDIInterface(function(midi) {\r\n            //open\r\n            that._midi = midi;\r\n            if (that._waiting) {\r\n                that.onStart();\r\n            }\r\n            that._waiting = false;\r\n        });\r\n    }\r\n\r\n    LGMIDIIn.MIDIInterface = MIDIInterface;\r\n\r\n    LGMIDIIn.title = \"MIDI Input\";\r\n    LGMIDIIn.desc = \"Reads MIDI from a input port\";\r\n    LGMIDIIn.color = MIDI_COLOR;\r\n\r\n    LGMIDIIn.prototype.getPropertyInfo = function(name) {\r\n        if (!this._midi) {\r\n            return;\r\n        }\r\n\r\n        if (name == \"port\") {\r\n            var values = {};\r\n            for (var i = 0; i < this._midi.input_ports_info.length; ++i) {\r\n                var input = this._midi.input_ports_info[i];\r\n                values[i] = i + \".- \" + input.name + \" version:\" + input.version;\r\n            }\r\n            return { type: \"enum\", values: values };\r\n        }\r\n    };\r\n\r\n    LGMIDIIn.prototype.onStart = function() {\r\n        if (this._midi) {\r\n            this._midi.openInputPort(\r\n                this.properties.port,\r\n                this.onMIDIEvent.bind(this)\r\n            );\r\n        } else {\r\n            this._waiting = true;\r\n        }\r\n    };\r\n\r\n    LGMIDIIn.prototype.onMIDIEvent = function(data, midi_event) {\r\n        this._last_midi_event = midi_event;\r\n        this.boxcolor = \"#AFA\";\r\n        this._last_time = LiteGraph.getTime();\r\n        this.trigger(\"on_midi\", midi_event);\r\n        if (midi_event.cmd == MIDIEvent.NOTEON) {\r\n            this.trigger(\"on_noteon\", midi_event);\r\n        } else if (midi_event.cmd == MIDIEvent.NOTEOFF) {\r\n            this.trigger(\"on_noteoff\", midi_event);\r\n        } else if (midi_event.cmd == MIDIEvent.CONTROLLERCHANGE) {\r\n            this.trigger(\"on_cc\", midi_event);\r\n        } else if (midi_event.cmd == MIDIEvent.PROGRAMCHANGE) {\r\n            this.trigger(\"on_pc\", midi_event);\r\n        } else if (midi_event.cmd == MIDIEvent.PITCHBEND) {\r\n            this.trigger(\"on_pitchbend\", midi_event);\r\n        }\r\n    };\r\n\r\n    LGMIDIIn.prototype.onDrawBackground = function(ctx) {\r\n        this.boxcolor = \"#AAA\";\r\n        if (!this.flags.collapsed && this._last_midi_event) {\r\n            ctx.fillStyle = \"white\";\r\n            var now = LiteGraph.getTime();\r\n            var f = 1.0 - Math.max(0, (now - this._last_time) * 0.001);\r\n            if (f > 0) {\r\n                var t = ctx.globalAlpha;\r\n                ctx.globalAlpha *= f;\r\n                ctx.font = \"12px Tahoma\";\r\n                ctx.fillText(\r\n                    this._last_midi_event.toString(),\r\n                    2,\r\n                    this.size[1] * 0.5 + 3\r\n                );\r\n                //ctx.fillRect(0,0,this.size[0],this.size[1]);\r\n                ctx.globalAlpha = t;\r\n            }\r\n        }\r\n    };\r\n\r\n    LGMIDIIn.prototype.onExecute = function() {\r\n        if (this.outputs) {\r\n            var last = this._last_midi_event;\r\n            for (var i = 0; i < this.outputs.length; ++i) {\r\n                var output = this.outputs[i];\r\n                var v = null;\r\n                switch (output.name) {\r\n                    case \"midi\":\r\n                        v = this._midi;\r\n                        break;\r\n                    case \"last_midi\":\r\n                        v = last;\r\n                        break;\r\n                    default:\r\n                        continue;\r\n                }\r\n                this.setOutputData(i, v);\r\n            }\r\n        }\r\n    };\r\n\r\n    LGMIDIIn.prototype.onGetOutputs = function() {\r\n        return [\r\n            [\"last_midi\", \"midi\"],\r\n            [\"on_midi\", LiteGraph.EVENT],\r\n            [\"on_noteon\", LiteGraph.EVENT],\r\n            [\"on_noteoff\", LiteGraph.EVENT],\r\n            [\"on_cc\", LiteGraph.EVENT],\r\n            [\"on_pc\", LiteGraph.EVENT],\r\n            [\"on_pitchbend\", LiteGraph.EVENT]\r\n        ];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/input\", LGMIDIIn);\r\n\r\n    function LGMIDIOut() {\r\n        this.addInput(\"send\", LiteGraph.EVENT);\r\n        this.properties = { port: 0 };\r\n\r\n        var that = this;\r\n        new MIDIInterface(function(midi) {\r\n            that._midi = midi;\r\n\t\t\tthat.widget.options.values = that.getMIDIOutputs();\r\n        });\r\n\t\tthis.widget = this.addWidget(\"combo\",\"Device\",this.properties.port,{ property: \"port\", values: this.getMIDIOutputs.bind(this) });\r\n\t\tthis.size = [340,60];\r\n    }\r\n\r\n    LGMIDIOut.MIDIInterface = MIDIInterface;\r\n\r\n    LGMIDIOut.title = \"MIDI Output\";\r\n    LGMIDIOut.desc = \"Sends MIDI to output channel\";\r\n    LGMIDIOut.color = MIDI_COLOR;\r\n\r\n    LGMIDIOut.prototype.onGetPropertyInfo = function(name) {\r\n        if (!this._midi) {\r\n            return;\r\n        }\r\n\r\n        if (name == \"port\") {\r\n\t\t\tvar values = this.getMIDIOutputs();\r\n            return { type: \"enum\", values: values };\r\n        }\r\n    };\r\n\tLGMIDIOut.default_ports = {0:\"unknown\"};\r\n\r\n\tLGMIDIOut.prototype.getMIDIOutputs = function()\r\n\t{\r\n\t\tvar values = {};\r\n\t\tif(!this._midi)\r\n\t\t\treturn LGMIDIOut.default_ports;\r\n\t\tif(this._midi.output_ports_info)\r\n\t\tfor (var i = 0; i < this._midi.output_ports_info.length; ++i) {\r\n\t\t\tvar output = this._midi.output_ports_info[i];\r\n\t\t\tif(!output)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar name = i + \".- \" + output.name + \" version:\" + output.version;\r\n\t\t\tvalues[i] = name;\r\n\t\t}\r\n\t\treturn values;\r\n\t}\r\n\r\n    LGMIDIOut.prototype.onAction = function(event, midi_event) {\r\n        //console.log(midi_event);\r\n        if (!this._midi) {\r\n            return;\r\n        }\r\n        if (event == \"send\") {\r\n            this._midi.sendMIDI(this.properties.port, midi_event);\r\n        }\r\n        this.trigger(\"midi\", midi_event);\r\n    };\r\n\r\n    LGMIDIOut.prototype.onGetInputs = function() {\r\n        return [[\"send\", LiteGraph.ACTION]];\r\n    };\r\n\r\n    LGMIDIOut.prototype.onGetOutputs = function() {\r\n        return [[\"on_midi\", LiteGraph.EVENT]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/output\", LGMIDIOut);\r\n\r\n\r\n    function LGMIDIShow() {\r\n        this.addInput(\"on_midi\", LiteGraph.EVENT);\r\n        this._str = \"\";\r\n        this.size = [200, 40];\r\n    }\r\n\r\n    LGMIDIShow.title = \"MIDI Show\";\r\n    LGMIDIShow.desc = \"Shows MIDI in the graph\";\r\n    LGMIDIShow.color = MIDI_COLOR;\r\n\r\n    LGMIDIShow.prototype.getTitle = function() {\r\n        if (this.flags.collapsed) {\r\n            return this._str;\r\n        }\r\n        return this.title;\r\n    };\r\n\r\n    LGMIDIShow.prototype.onAction = function(event, midi_event) {\r\n        if (!midi_event) {\r\n            return;\r\n        }\r\n        if (midi_event.constructor === MIDIEvent) {\r\n            this._str = midi_event.toString();\r\n        } else {\r\n            this._str = \"???\";\r\n        }\r\n    };\r\n\r\n    LGMIDIShow.prototype.onDrawForeground = function(ctx) {\r\n        if (!this._str || this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        ctx.font = \"30px Arial\";\r\n        ctx.fillText(this._str, 10, this.size[1] * 0.8);\r\n    };\r\n\r\n    LGMIDIShow.prototype.onGetInputs = function() {\r\n        return [[\"in\", LiteGraph.ACTION]];\r\n    };\r\n\r\n    LGMIDIShow.prototype.onGetOutputs = function() {\r\n        return [[\"on_midi\", LiteGraph.EVENT]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/show\", LGMIDIShow);\r\n\r\n    function LGMIDIFilter() {\r\n        this.properties = {\r\n            channel: -1,\r\n            cmd: -1,\r\n            min_value: -1,\r\n            max_value: -1\r\n        };\r\n\r\n        var that = this;\r\n        this._learning = false;\r\n        this.addWidget(\"button\", \"Learn\", \"\", function() {\r\n            that._learning = true;\r\n            that.boxcolor = \"#FA3\";\r\n        });\r\n\r\n        this.addInput(\"in\", LiteGraph.EVENT);\r\n        this.addOutput(\"on_midi\", LiteGraph.EVENT);\r\n        this.boxcolor = \"#AAA\";\r\n    }\r\n\r\n    LGMIDIFilter.title = \"MIDI Filter\";\r\n    LGMIDIFilter.desc = \"Filters MIDI messages\";\r\n    LGMIDIFilter.color = MIDI_COLOR;\r\n\r\n    LGMIDIFilter[\"@cmd\"] = {\r\n        type: \"enum\",\r\n        title: \"Command\",\r\n        values: MIDIEvent.commands_reversed\r\n    };\r\n\r\n    LGMIDIFilter.prototype.getTitle = function() {\r\n        var str = null;\r\n        if (this.properties.cmd == -1) {\r\n            str = \"Nothing\";\r\n        } else {\r\n            str = MIDIEvent.commands_short[this.properties.cmd] || \"Unknown\";\r\n        }\r\n\r\n        if (\r\n            this.properties.min_value != -1 &&\r\n            this.properties.max_value != -1\r\n        ) {\r\n            str +=\r\n                \" \" +\r\n                (this.properties.min_value == this.properties.max_value\r\n                    ? this.properties.max_value\r\n                    : this.properties.min_value +\r\n                      \"..\" +\r\n                      this.properties.max_value);\r\n        }\r\n\r\n        return \"Filter: \" + str;\r\n    };\r\n\r\n    LGMIDIFilter.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"cmd\") {\r\n            var num = Number(value);\r\n            if (isNaN(num)) {\r\n                num = MIDIEvent.commands[value] || 0;\r\n            }\r\n            this.properties.cmd = num;\r\n        }\r\n    };\r\n\r\n    LGMIDIFilter.prototype.onAction = function(event, midi_event) {\r\n        if (!midi_event || midi_event.constructor !== MIDIEvent) {\r\n            return;\r\n        }\r\n\r\n        if (this._learning) {\r\n            this._learning = false;\r\n            this.boxcolor = \"#AAA\";\r\n            this.properties.channel = midi_event.channel;\r\n            this.properties.cmd = midi_event.cmd;\r\n            this.properties.min_value = this.properties.max_value =\r\n                midi_event.data[1];\r\n        } else {\r\n            if (\r\n                this.properties.channel != -1 &&\r\n                midi_event.channel != this.properties.channel\r\n            ) {\r\n                return;\r\n            }\r\n            if (\r\n                this.properties.cmd != -1 &&\r\n                midi_event.cmd != this.properties.cmd\r\n            ) {\r\n                return;\r\n            }\r\n            if (\r\n                this.properties.min_value != -1 &&\r\n                midi_event.data[1] < this.properties.min_value\r\n            ) {\r\n                return;\r\n            }\r\n            if (\r\n                this.properties.max_value != -1 &&\r\n                midi_event.data[1] > this.properties.max_value\r\n            ) {\r\n                return;\r\n            }\r\n        }\r\n\r\n        this.trigger(\"on_midi\", midi_event);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/filter\", LGMIDIFilter);\r\n\r\n    function LGMIDIEvent() {\r\n        this.properties = {\r\n            channel: 0,\r\n            cmd: 144, //0x90\r\n            value1: 1,\r\n            value2: 1\r\n        };\r\n\r\n        this.addInput(\"send\", LiteGraph.EVENT);\r\n        this.addInput(\"assign\", LiteGraph.EVENT);\r\n        this.addOutput(\"on_midi\", LiteGraph.EVENT);\r\n\r\n        this.midi_event = new MIDIEvent();\r\n        this.gate = false;\r\n    }\r\n\r\n    LGMIDIEvent.title = \"MIDIEvent\";\r\n    LGMIDIEvent.desc = \"Create a MIDI Event\";\r\n    LGMIDIEvent.color = MIDI_COLOR;\r\n\r\n    LGMIDIEvent.prototype.onAction = function(event, midi_event) {\r\n        if (event == \"assign\") {\r\n            this.properties.channel = midi_event.channel;\r\n            this.properties.cmd = midi_event.cmd;\r\n            this.properties.value1 = midi_event.data[1];\r\n            this.properties.value2 = midi_event.data[2];\r\n            if (midi_event.cmd == MIDIEvent.NOTEON) {\r\n                this.gate = true;\r\n            } else if (midi_event.cmd == MIDIEvent.NOTEOFF) {\r\n                this.gate = false;\r\n            }\r\n            return;\r\n        }\r\n\r\n        //send\r\n        var midi_event = this.midi_event;\r\n        midi_event.channel = this.properties.channel;\r\n        if (this.properties.cmd && this.properties.cmd.constructor === String) {\r\n            midi_event.setCommandFromString(this.properties.cmd);\r\n        } else {\r\n            midi_event.cmd = this.properties.cmd;\r\n        }\r\n        midi_event.data[0] = midi_event.cmd | midi_event.channel;\r\n        midi_event.data[1] = Number(this.properties.value1);\r\n        midi_event.data[2] = Number(this.properties.value2);\r\n\r\n        this.trigger(\"on_midi\", midi_event);\r\n    };\r\n\r\n    LGMIDIEvent.prototype.onExecute = function() {\r\n        var props = this.properties;\r\n\r\n        if (this.inputs) {\r\n            for (var i = 0; i < this.inputs.length; ++i) {\r\n                var input = this.inputs[i];\r\n                if (input.link == -1) {\r\n                    continue;\r\n                }\r\n                switch (input.name) {\r\n                    case \"note\":\r\n                        var v = this.getInputData(i);\r\n                        if (v != null) {\r\n                            if (v.constructor === String) {\r\n                                v = MIDIEvent.NoteStringToPitch(v);\r\n                            }\r\n                            this.properties.value1 = (v | 0) % 255;\r\n                        }\r\n                        break;\r\n                    case \"cmd\":\r\n                        var v = this.getInputData(i);\r\n                        if (v != null) {\r\n                            this.properties.cmd = v;\r\n                        }\r\n                        break;\r\n                    case \"value1\":\r\n                        var v = this.getInputData(i);\r\n                        if (v != null) {\r\n                            this.properties.value1 = clamp(v|0,0,127);\r\n                        }\r\n                        break;\r\n                    case \"value2\":\r\n                        var v = this.getInputData(i);\r\n                        if (v != null) {\r\n                            this.properties.value2 = clamp(v|0,0,127);\r\n                        }\r\n                        break;\r\n                }\r\n            }\r\n        }\r\n\r\n        if (this.outputs) {\r\n            for (var i = 0; i < this.outputs.length; ++i) {\r\n                var output = this.outputs[i];\r\n                var v = null;\r\n                switch (output.name) {\r\n                    case \"midi\":\r\n                        v = new MIDIEvent();\r\n                        v.setup([props.cmd, props.value1, props.value2]);\r\n                        v.channel = props.channel;\r\n                        break;\r\n                    case \"command\":\r\n                        v = props.cmd;\r\n                        break;\r\n                    case \"cc\":\r\n                        v = props.value1;\r\n                        break;\r\n                    case \"cc_value\":\r\n                        v = props.value2;\r\n                        break;\r\n                    case \"note\":\r\n                        v =\r\n                            props.cmd == MIDIEvent.NOTEON ||\r\n                            props.cmd == MIDIEvent.NOTEOFF\r\n                                ? props.value1\r\n                                : null;\r\n                        break;\r\n                    case \"velocity\":\r\n                        v = props.cmd == MIDIEvent.NOTEON ? props.value2 : null;\r\n                        break;\r\n                    case \"pitch\":\r\n                        v =\r\n                            props.cmd == MIDIEvent.NOTEON\r\n                                ? MIDIEvent.computePitch(props.value1)\r\n                                : null;\r\n                        break;\r\n                    case \"pitchbend\":\r\n                        v =\r\n                            props.cmd == MIDIEvent.PITCHBEND\r\n                                ? MIDIEvent.computePitchBend(\r\n                                      props.value1,\r\n                                      props.value2\r\n                                  )\r\n                                : null;\r\n                        break;\r\n                    case \"gate\":\r\n                        v = this.gate;\r\n                        break;\r\n                    default:\r\n                        continue;\r\n                }\r\n                if (v !== null) {\r\n                    this.setOutputData(i, v);\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    LGMIDIEvent.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"cmd\") {\r\n            this.properties.cmd = MIDIEvent.computeCommandFromString(value);\r\n        }\r\n    };\r\n\r\n    LGMIDIEvent.prototype.onGetInputs = function() {\r\n        return [[\"cmd\", \"number\"],[\"note\", \"number\"],[\"value1\", \"number\"],[\"value2\", \"number\"]];\r\n    };\r\n\r\n    LGMIDIEvent.prototype.onGetOutputs = function() {\r\n        return [\r\n            [\"midi\", \"midi\"],\r\n            [\"on_midi\", LiteGraph.EVENT],\r\n            [\"command\", \"number\"],\r\n            [\"note\", \"number\"],\r\n            [\"velocity\", \"number\"],\r\n            [\"cc\", \"number\"],\r\n            [\"cc_value\", \"number\"],\r\n            [\"pitch\", \"number\"],\r\n            [\"gate\", \"bool\"],\r\n            [\"pitchbend\", \"number\"]\r\n        ];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/event\", LGMIDIEvent);\r\n\r\n    function LGMIDICC() {\r\n        this.properties = {\r\n            //\t\tchannel: 0,\r\n            cc: 1,\r\n            value: 0\r\n        };\r\n\r\n        this.addOutput(\"value\", \"number\");\r\n    }\r\n\r\n    LGMIDICC.title = \"MIDICC\";\r\n    LGMIDICC.desc = \"gets a Controller Change\";\r\n    LGMIDICC.color = MIDI_COLOR;\r\n\r\n    LGMIDICC.prototype.onExecute = function() {\r\n        var props = this.properties;\r\n        if (MIDIInterface.input) {\r\n            this.properties.value =\r\n                MIDIInterface.input.state.cc[this.properties.cc];\r\n        }\r\n        this.setOutputData(0, this.properties.value);\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/cc\", LGMIDICC);\r\n\r\n    function LGMIDIGenerator() {\r\n        this.addInput(\"generate\", LiteGraph.ACTION);\r\n        this.addInput(\"scale\", \"string\");\r\n        this.addInput(\"octave\", \"number\");\r\n        this.addOutput(\"note\", LiteGraph.EVENT);\r\n        this.properties = {\r\n            notes: \"A,A#,B,C,C#,D,D#,E,F,F#,G,G#\",\r\n            octave: 2,\r\n            duration: 0.5,\r\n            mode: \"sequence\"\r\n        };\r\n\r\n        this.notes_pitches = LGMIDIGenerator.processScale(\r\n            this.properties.notes\r\n        );\r\n        this.sequence_index = 0;\r\n    }\r\n\r\n    LGMIDIGenerator.title = \"MIDI Generator\";\r\n    LGMIDIGenerator.desc = \"Generates a random MIDI note\";\r\n    LGMIDIGenerator.color = MIDI_COLOR;\r\n\r\n    LGMIDIGenerator.processScale = function(scale) {\r\n        var notes = scale.split(\",\");\r\n        for (var i = 0; i < notes.length; ++i) {\r\n            var n = notes[i];\r\n            if ((n.length == 2 && n[1] != \"#\") || n.length > 2) {\r\n                notes[i] = -LiteGraph.MIDIEvent.NoteStringToPitch(n);\r\n            } else {\r\n                notes[i] = MIDIEvent.note_to_index[n] || 0;\r\n            }\r\n        }\r\n        return notes;\r\n    };\r\n\r\n    LGMIDIGenerator.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"notes\") {\r\n            this.notes_pitches = LGMIDIGenerator.processScale(value);\r\n        }\r\n    };\r\n\r\n    LGMIDIGenerator.prototype.onExecute = function() {\r\n        var octave = this.getInputData(2);\r\n        if (octave != null) {\r\n            this.properties.octave = octave;\r\n        }\r\n\r\n        var scale = this.getInputData(1);\r\n        if (scale) {\r\n            this.notes_pitches = LGMIDIGenerator.processScale(scale);\r\n        }\r\n    };\r\n\r\n    LGMIDIGenerator.prototype.onAction = function(event, midi_event) {\r\n        //var range = this.properties.max - this.properties.min;\r\n        //var pitch = this.properties.min + ((Math.random() * range)|0);\r\n        var pitch = 0;\r\n        var range = this.notes_pitches.length;\r\n        var index = 0;\r\n\r\n        if (this.properties.mode == \"sequence\") {\r\n            index = this.sequence_index = (this.sequence_index + 1) % range;\r\n        } else if (this.properties.mode == \"random\") {\r\n            index = Math.floor(Math.random() * range);\r\n        }\r\n\r\n        var note = this.notes_pitches[index];\r\n        if (note >= 0) {\r\n            pitch = note + (this.properties.octave - 1) * 12 + 33;\r\n        } else {\r\n            pitch = -note;\r\n        }\r\n\r\n        var midi_event = new MIDIEvent();\r\n        midi_event.setup([MIDIEvent.NOTEON, pitch, 10]);\r\n        var duration = this.properties.duration || 1;\r\n        this.trigger(\"note\", midi_event);\r\n\r\n        //noteoff\r\n        setTimeout(\r\n            function() {\r\n                var midi_event = new MIDIEvent();\r\n                midi_event.setup([MIDIEvent.NOTEOFF, pitch, 0]);\r\n                this.trigger(\"note\", midi_event);\r\n            }.bind(this),\r\n            duration * 1000\r\n        );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/generator\", LGMIDIGenerator);\r\n\r\n    function LGMIDITranspose() {\r\n        this.properties = {\r\n            amount: 0\r\n        };\r\n        this.addInput(\"in\", LiteGraph.ACTION);\r\n        this.addInput(\"amount\", \"number\");\r\n        this.addOutput(\"out\", LiteGraph.EVENT);\r\n\r\n        this.midi_event = new MIDIEvent();\r\n    }\r\n\r\n    LGMIDITranspose.title = \"MIDI Transpose\";\r\n    LGMIDITranspose.desc = \"Transpose a MIDI note\";\r\n    LGMIDITranspose.color = MIDI_COLOR;\r\n\r\n    LGMIDITranspose.prototype.onAction = function(event, midi_event) {\r\n        if (!midi_event || midi_event.constructor !== MIDIEvent) {\r\n            return;\r\n        }\r\n\r\n        if (\r\n            midi_event.data[0] == MIDIEvent.NOTEON ||\r\n            midi_event.data[0] == MIDIEvent.NOTEOFF\r\n        ) {\r\n            this.midi_event = new MIDIEvent();\r\n            this.midi_event.setup(midi_event.data);\r\n            this.midi_event.data[1] = Math.round(\r\n                this.midi_event.data[1] + this.properties.amount\r\n            );\r\n            this.trigger(\"out\", this.midi_event);\r\n        } else {\r\n            this.trigger(\"out\", midi_event);\r\n        }\r\n    };\r\n\r\n    LGMIDITranspose.prototype.onExecute = function() {\r\n        var amount = this.getInputData(1);\r\n        if (amount != null) {\r\n            this.properties.amount = amount;\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/transpose\", LGMIDITranspose);\r\n\r\n    function LGMIDIQuantize() {\r\n        this.properties = {\r\n            scale: \"A,A#,B,C,C#,D,D#,E,F,F#,G,G#\"\r\n        };\r\n        this.addInput(\"note\", LiteGraph.ACTION);\r\n        this.addInput(\"scale\", \"string\");\r\n        this.addOutput(\"out\", LiteGraph.EVENT);\r\n\r\n        this.valid_notes = new Array(12);\r\n        this.offset_notes = new Array(12);\r\n        this.processScale(this.properties.scale);\r\n    }\r\n\r\n    LGMIDIQuantize.title = \"MIDI Quantize Pitch\";\r\n    LGMIDIQuantize.desc = \"Transpose a MIDI note tp fit an scale\";\r\n    LGMIDIQuantize.color = MIDI_COLOR;\r\n\r\n    LGMIDIQuantize.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"scale\") {\r\n            this.processScale(value);\r\n        }\r\n    };\r\n\r\n    LGMIDIQuantize.prototype.processScale = function(scale) {\r\n        this._current_scale = scale;\r\n        this.notes_pitches = LGMIDIGenerator.processScale(scale);\r\n        for (var i = 0; i < 12; ++i) {\r\n            this.valid_notes[i] = this.notes_pitches.indexOf(i) != -1;\r\n        }\r\n        for (var i = 0; i < 12; ++i) {\r\n            if (this.valid_notes[i]) {\r\n                this.offset_notes[i] = 0;\r\n                continue;\r\n            }\r\n            for (var j = 1; j < 12; ++j) {\r\n                if (this.valid_notes[(i - j) % 12]) {\r\n                    this.offset_notes[i] = -j;\r\n                    break;\r\n                }\r\n                if (this.valid_notes[(i + j) % 12]) {\r\n                    this.offset_notes[i] = j;\r\n                    break;\r\n                }\r\n            }\r\n        }\r\n    };\r\n\r\n    LGMIDIQuantize.prototype.onAction = function(event, midi_event) {\r\n        if (!midi_event || midi_event.constructor !== MIDIEvent) {\r\n            return;\r\n        }\r\n\r\n        if (\r\n            midi_event.data[0] == MIDIEvent.NOTEON ||\r\n            midi_event.data[0] == MIDIEvent.NOTEOFF\r\n        ) {\r\n            this.midi_event = new MIDIEvent();\r\n            this.midi_event.setup(midi_event.data);\r\n            var note = midi_event.note;\r\n            var index = MIDIEvent.note_to_index[note];\r\n            var offset = this.offset_notes[index];\r\n            this.midi_event.data[1] += offset;\r\n            this.trigger(\"out\", this.midi_event);\r\n        } else {\r\n            this.trigger(\"out\", midi_event);\r\n        }\r\n    };\r\n\r\n    LGMIDIQuantize.prototype.onExecute = function() {\r\n        var scale = this.getInputData(1);\r\n        if (scale != null && scale != this._current_scale) {\r\n            this.processScale(scale);\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/quantize\", LGMIDIQuantize);\r\n\r\n\tfunction LGMIDIFromFile() {\r\n        this.properties = {\r\n            url: \"\",\r\n\t\t\tautoplay: true\r\n        };\r\n\r\n        this.addInput(\"play\", LiteGraph.ACTION);\r\n        this.addInput(\"pause\", LiteGraph.ACTION);\r\n        this.addOutput(\"note\", LiteGraph.EVENT);\r\n\t\tthis._midi = null;\r\n\t\tthis._current_time = 0;\r\n\t\tthis._playing = false;\r\n\r\n        if (typeof MidiParser == \"undefined\") {\r\n            console.error(\r\n                \"midi-parser.js not included, LGMidiPlay requires that library: https://raw.githubusercontent.com/colxi/midi-parser-js/master/src/main.js\"\r\n            );\r\n            this.boxcolor = \"red\";\r\n\t\t}\r\n\r\n\t}\r\n\r\n    LGMIDIFromFile.title = \"MIDI fromFile\";\r\n    LGMIDIFromFile.desc = \"Plays a MIDI file\";\r\n    LGMIDIFromFile.color = MIDI_COLOR;\r\n\r\n\tLGMIDIFromFile.prototype.onAction = function( name )\r\n\t{\r\n\t\tif(name == \"play\")\r\n\t\t\tthis.play();\r\n\t\telse if(name == \"pause\")\r\n\t\t\tthis._playing = !this._playing;\r\n\t}\r\n\r\n\tLGMIDIFromFile.prototype.onPropertyChanged = function(name,value)\r\n\t{\r\n\t\tif(name == \"url\")\r\n\t\t\tthis.loadMIDIFile(value);\r\n\t}\r\n\r\n    LGMIDIFromFile.prototype.onExecute = function() {\r\n\t\tif(!this._midi)\r\n\t\t\treturn;\r\n\r\n\t\tif(!this._playing)\r\n\t\t\treturn;\r\n\r\n\t\tthis._current_time += this.graph.elapsed_time;\r\n\t\tvar current_time = this._current_time * 100;\r\n\r\n\t\tfor(var i = 0; i < this._midi.tracks; ++i)\r\n\t\t{\r\n\t\t\tvar track = this._midi.track[i];\r\n\t\t\tif(!track._last_pos)\r\n\t\t\t{\r\n\t\t\t\ttrack._last_pos = 0;\r\n\t\t\t\ttrack._time = 0;\r\n\t\t\t}\r\n\r\n\t\t\tvar elem = track.event[ track._last_pos ];\r\n\t\t\tif(elem && (track._time + elem.deltaTime) <= current_time )\r\n\t\t\t{\r\n\t\t\t\ttrack._last_pos++;\r\n\t\t\t\ttrack._time += elem.deltaTime;\r\n\r\n\t\t\t\tif(elem.data)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar midi_cmd = elem.type << 4 + elem.channel;\r\n\t\t\t\t\tvar midi_event = new MIDIEvent();\r\n\t\t\t\t\tmidi_event.setup([midi_cmd, elem.data[0], elem.data[1]]);\r\n\t\t\t\t\tthis.trigger(\"note\", midi_event);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t}\r\n    };\r\n\r\n\tLGMIDIFromFile.prototype.play = function()\r\n\t{\r\n\t\tthis._playing = true;\r\n\t\tthis._current_time = 0;\r\n\t\tif(!this._midi)\r\n\t\t\treturn;\r\n\r\n\t\tfor(var i = 0; i < this._midi.tracks; ++i)\r\n\t\t{\r\n\t\t\tvar track = this._midi.track[i];\r\n\t\t\ttrack._last_pos = 0;\r\n\t\t\ttrack._time = 0;\r\n\t\t}\t\t\r\n\t}\r\n\r\n\tLGMIDIFromFile.prototype.loadMIDIFile = function(url)\r\n\t{\r\n\t\tvar that = this;\r\n\t\tLiteGraph.fetchFile( url, \"arraybuffer\", function(data)\r\n\t\t{\r\n\t\t\tthat.boxcolor = \"#AFA\";\r\n\t\t\tthat._midi = MidiParser.parse( new Uint8Array(data) );\r\n\t\t\tif(that.properties.autoplay)\r\n\t\t\t\tthat.play();\r\n\t\t}, function(err){\r\n\t\t\tthat.boxcolor = \"#FAA\";\r\n\t\t\tthat._midi = null;\r\n\t\t});\r\n\t}\r\n\r\n\tLGMIDIFromFile.prototype.onDropFile = function(file)\r\n\t{\r\n\t\tthis.properties.url = \"\";\r\n\t\tthis.loadMIDIFile( file );\r\n\t}\r\n\r\n    LiteGraph.registerNodeType(\"midi/fromFile\", LGMIDIFromFile);\r\n\r\n\r\n    function LGMIDIPlay() {\r\n        this.properties = {\r\n            volume: 0.5,\r\n            duration: 1\r\n        };\r\n        this.addInput(\"note\", LiteGraph.ACTION);\r\n        this.addInput(\"volume\", \"number\");\r\n        this.addInput(\"duration\", \"number\");\r\n        this.addOutput(\"note\", LiteGraph.EVENT);\r\n\r\n        if (typeof AudioSynth == \"undefined\") {\r\n            console.error(\r\n                \"Audiosynth.js not included, LGMidiPlay requires that library\"\r\n            );\r\n            this.boxcolor = \"red\";\r\n        } else {\r\n            var Synth = (this.synth = new AudioSynth());\r\n            this.instrument = Synth.createInstrument(\"piano\");\r\n        }\r\n    }\r\n\r\n    LGMIDIPlay.title = \"MIDI Play\";\r\n    LGMIDIPlay.desc = \"Plays a MIDI note\";\r\n    LGMIDIPlay.color = MIDI_COLOR;\r\n\r\n    LGMIDIPlay.prototype.onAction = function(event, midi_event) {\r\n        if (!midi_event || midi_event.constructor !== MIDIEvent) {\r\n            return;\r\n        }\r\n\r\n        if (this.instrument && midi_event.data[0] == MIDIEvent.NOTEON) {\r\n            var note = midi_event.note; //C#\r\n            if (!note || note == \"undefined\" || note.constructor !== String) {\r\n                return;\r\n            }\r\n            this.instrument.play(\r\n                note,\r\n                midi_event.octave,\r\n                this.properties.duration,\r\n                this.properties.volume\r\n            );\r\n        }\r\n        this.trigger(\"note\", midi_event);\r\n    };\r\n\r\n    LGMIDIPlay.prototype.onExecute = function() {\r\n        var volume = this.getInputData(1);\r\n        if (volume != null) {\r\n            this.properties.volume = volume;\r\n        }\r\n\r\n        var duration = this.getInputData(2);\r\n        if (duration != null) {\r\n            this.properties.duration = duration;\r\n        }\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/play\", LGMIDIPlay);\r\n\r\n    function LGMIDIKeys() {\r\n        this.properties = {\r\n            num_octaves: 2,\r\n            start_octave: 2\r\n        };\r\n        this.addInput(\"note\", LiteGraph.ACTION);\r\n        this.addInput(\"reset\", LiteGraph.ACTION);\r\n        this.addOutput(\"note\", LiteGraph.EVENT);\r\n        this.size = [400, 100];\r\n        this.keys = [];\r\n        this._last_key = -1;\r\n    }\r\n\r\n    LGMIDIKeys.title = \"MIDI Keys\";\r\n    LGMIDIKeys.desc = \"Keyboard to play notes\";\r\n    LGMIDIKeys.color = MIDI_COLOR;\r\n\r\n    LGMIDIKeys.keys = [\r\n        { x: 0, w: 1, h: 1, t: 0 },\r\n        { x: 0.75, w: 0.5, h: 0.6, t: 1 },\r\n        { x: 1, w: 1, h: 1, t: 0 },\r\n        { x: 1.75, w: 0.5, h: 0.6, t: 1 },\r\n        { x: 2, w: 1, h: 1, t: 0 },\r\n        { x: 2.75, w: 0.5, h: 0.6, t: 1 },\r\n        { x: 3, w: 1, h: 1, t: 0 },\r\n        { x: 4, w: 1, h: 1, t: 0 },\r\n        { x: 4.75, w: 0.5, h: 0.6, t: 1 },\r\n        { x: 5, w: 1, h: 1, t: 0 },\r\n        { x: 5.75, w: 0.5, h: 0.6, t: 1 },\r\n        { x: 6, w: 1, h: 1, t: 0 }\r\n    ];\r\n\r\n    LGMIDIKeys.prototype.onDrawForeground = function(ctx) {\r\n        if (this.flags.collapsed) {\r\n            return;\r\n        }\r\n\r\n        var num_keys = this.properties.num_octaves * 12;\r\n        this.keys.length = num_keys;\r\n        var key_width = this.size[0] / (this.properties.num_octaves * 7);\r\n        var key_height = this.size[1];\r\n\r\n        ctx.globalAlpha = 1;\r\n\r\n        for (\r\n            var k = 0;\r\n            k < 2;\r\n            k++ //draw first whites (0) then blacks (1)\r\n        ) {\r\n            for (var i = 0; i < num_keys; ++i) {\r\n                var key_info = LGMIDIKeys.keys[i % 12];\r\n                if (key_info.t != k) {\r\n                    continue;\r\n                }\r\n                var octave = Math.floor(i / 12);\r\n                var x = octave * 7 * key_width + key_info.x * key_width;\r\n                if (k == 0) {\r\n                    ctx.fillStyle = this.keys[i] ? \"#CCC\" : \"white\";\r\n                } else {\r\n                    ctx.fillStyle = this.keys[i] ? \"#333\" : \"black\";\r\n                }\r\n                ctx.fillRect(\r\n                    x + 1,\r\n                    0,\r\n                    key_width * key_info.w - 2,\r\n                    key_height * key_info.h\r\n                );\r\n            }\r\n        }\r\n    };\r\n\r\n    LGMIDIKeys.prototype.getKeyIndex = function(pos) {\r\n        var num_keys = this.properties.num_octaves * 12;\r\n        var key_width = this.size[0] / (this.properties.num_octaves * 7);\r\n        var key_height = this.size[1];\r\n\r\n        for (\r\n            var k = 1;\r\n            k >= 0;\r\n            k-- //test blacks first (1) then whites (0)\r\n        ) {\r\n            for (var i = 0; i < this.keys.length; ++i) {\r\n                var key_info = LGMIDIKeys.keys[i % 12];\r\n                if (key_info.t != k) {\r\n                    continue;\r\n                }\r\n                var octave = Math.floor(i / 12);\r\n                var x = octave * 7 * key_width + key_info.x * key_width;\r\n                var w = key_width * key_info.w;\r\n                var h = key_height * key_info.h;\r\n                if (pos[0] < x || pos[0] > x + w || pos[1] > h) {\r\n                    continue;\r\n                }\r\n                return i;\r\n            }\r\n        }\r\n        return -1;\r\n    };\r\n\r\n    LGMIDIKeys.prototype.onAction = function(event, params) {\r\n        if (event == \"reset\") {\r\n            for (var i = 0; i < this.keys.length; ++i) {\r\n                this.keys[i] = false;\r\n            }\r\n            return;\r\n        }\r\n\r\n        if (!params || params.constructor !== MIDIEvent) {\r\n            return;\r\n        }\r\n        var midi_event = params;\r\n        var start_note = (this.properties.start_octave - 1) * 12 + 29;\r\n        var index = midi_event.data[1] - start_note;\r\n        if (index >= 0 && index < this.keys.length) {\r\n            if (midi_event.data[0] == MIDIEvent.NOTEON) {\r\n                this.keys[index] = true;\r\n            } else if (midi_event.data[0] == MIDIEvent.NOTEOFF) {\r\n                this.keys[index] = false;\r\n            }\r\n        }\r\n\r\n        this.trigger(\"note\", midi_event);\r\n    };\r\n\r\n    LGMIDIKeys.prototype.onMouseDown = function(e, pos) {\r\n        if (pos[1] < 0) {\r\n            return;\r\n        }\r\n        var index = this.getKeyIndex(pos);\r\n        this.keys[index] = true;\r\n        this._last_key = index;\r\n        var pitch = (this.properties.start_octave - 1) * 12 + 29 + index;\r\n        var midi_event = new MIDIEvent();\r\n        midi_event.setup([MIDIEvent.NOTEON, pitch, 100]);\r\n        this.trigger(\"note\", midi_event);\r\n        return true;\r\n    };\r\n\r\n    LGMIDIKeys.prototype.onMouseMove = function(e, pos) {\r\n        if (pos[1] < 0 || this._last_key == -1) {\r\n            return;\r\n        }\r\n        this.setDirtyCanvas(true);\r\n        var index = this.getKeyIndex(pos);\r\n        if (this._last_key == index) {\r\n            return true;\r\n        }\r\n        this.keys[this._last_key] = false;\r\n        var pitch =\r\n            (this.properties.start_octave - 1) * 12 + 29 + this._last_key;\r\n        var midi_event = new MIDIEvent();\r\n        midi_event.setup([MIDIEvent.NOTEOFF, pitch, 100]);\r\n        this.trigger(\"note\", midi_event);\r\n\r\n        this.keys[index] = true;\r\n        var pitch = (this.properties.start_octave - 1) * 12 + 29 + index;\r\n        var midi_event = new MIDIEvent();\r\n        midi_event.setup([MIDIEvent.NOTEON, pitch, 100]);\r\n        this.trigger(\"note\", midi_event);\r\n\r\n        this._last_key = index;\r\n        return true;\r\n    };\r\n\r\n    LGMIDIKeys.prototype.onMouseUp = function(e, pos) {\r\n        if (pos[1] < 0) {\r\n            return;\r\n        }\r\n        var index = this.getKeyIndex(pos);\r\n        this.keys[index] = false;\r\n        this._last_key = -1;\r\n        var pitch = (this.properties.start_octave - 1) * 12 + 29 + index;\r\n        var midi_event = new MIDIEvent();\r\n        midi_event.setup([MIDIEvent.NOTEOFF, pitch, 100]);\r\n        this.trigger(\"note\", midi_event);\r\n        return true;\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"midi/keys\", LGMIDIKeys);\r\n\r\n    function now() {\r\n        return window.performance.now();\r\n    }\r\n})(this);\r\n"
  },
  {
    "path": "src/nodes/network.js",
    "content": "//event related nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    function LGWebSocket() {\r\n        this.size = [60, 20];\r\n        this.addInput(\"send\", LiteGraph.ACTION);\r\n        this.addOutput(\"received\", LiteGraph.EVENT);\r\n        this.addInput(\"in\", 0);\r\n        this.addOutput(\"out\", 0);\r\n        this.properties = {\r\n            url: \"\",\r\n            room: \"lgraph\", //allows to filter messages,\r\n            only_send_changes: true\r\n        };\r\n        this._ws = null;\r\n        this._last_sent_data = [];\r\n        this._last_received_data = [];\r\n    }\r\n\r\n    LGWebSocket.title = \"WebSocket\";\r\n    LGWebSocket.desc = \"Send data through a websocket\";\r\n\r\n    LGWebSocket.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"url\") {\r\n            this.connectSocket();\r\n        }\r\n    };\r\n\r\n    LGWebSocket.prototype.onExecute = function() {\r\n        if (!this._ws && this.properties.url) {\r\n            this.connectSocket();\r\n        }\r\n\r\n        if (!this._ws || this._ws.readyState != WebSocket.OPEN) {\r\n            return;\r\n        }\r\n\r\n        var room = this.properties.room;\r\n        var only_changes = this.properties.only_send_changes;\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var data = this.getInputData(i);\r\n            if (data == null) {\r\n                continue;\r\n            }\r\n            var json;\r\n            try {\r\n                json = JSON.stringify({\r\n                    type: 0,\r\n                    room: room,\r\n                    channel: i,\r\n                    data: data\r\n                });\r\n            } catch (err) {\r\n                continue;\r\n            }\r\n            if (only_changes && this._last_sent_data[i] == json) {\r\n                continue;\r\n            }\r\n\r\n            this._last_sent_data[i] = json;\r\n            this._ws.send(json);\r\n        }\r\n\r\n        for (var i = 1; i < this.outputs.length; ++i) {\r\n            this.setOutputData(i, this._last_received_data[i]);\r\n        }\r\n\r\n        if (this.boxcolor == \"#AFA\") {\r\n            this.boxcolor = \"#6C6\";\r\n        }\r\n    };\r\n\r\n    LGWebSocket.prototype.connectSocket = function() {\r\n        var that = this;\r\n        var url = this.properties.url;\r\n        if (url.substr(0, 2) != \"ws\") {\r\n            url = \"ws://\" + url;\r\n        }\r\n        this._ws = new WebSocket(url);\r\n        this._ws.onopen = function() {\r\n            console.log(\"ready\");\r\n            that.boxcolor = \"#6C6\";\r\n        };\r\n        this._ws.onmessage = function(e) {\r\n            that.boxcolor = \"#AFA\";\r\n            var data = JSON.parse(e.data);\r\n            if (data.room && data.room != that.properties.room) {\r\n                return;\r\n            }\r\n            if (data.type == 1) {\r\n                if (\r\n                    data.data.object_class &&\r\n                    LiteGraph[data.data.object_class]\r\n                ) {\r\n                    var obj = null;\r\n                    try {\r\n                        obj = new LiteGraph[data.data.object_class](data.data);\r\n                        that.triggerSlot(0, obj);\r\n                    } catch (err) {\r\n                        return;\r\n                    }\r\n                } else {\r\n                    that.triggerSlot(0, data.data);\r\n                }\r\n            } else {\r\n                that._last_received_data[data.channel || 0] = data.data;\r\n            }\r\n        };\r\n        this._ws.onerror = function(e) {\r\n            console.log(\"couldnt connect to websocket\");\r\n            that.boxcolor = \"#E88\";\r\n        };\r\n        this._ws.onclose = function(e) {\r\n            console.log(\"connection closed\");\r\n            that.boxcolor = \"#000\";\r\n        };\r\n    };\r\n\r\n    LGWebSocket.prototype.send = function(data) {\r\n        if (!this._ws || this._ws.readyState != WebSocket.OPEN) {\r\n            return;\r\n        }\r\n        this._ws.send(JSON.stringify({ type: 1, msg: data }));\r\n    };\r\n\r\n    LGWebSocket.prototype.onAction = function(action, param) {\r\n        if (!this._ws || this._ws.readyState != WebSocket.OPEN) {\r\n            return;\r\n        }\r\n        this._ws.send({\r\n            type: 1,\r\n            room: this.properties.room,\r\n            action: action,\r\n            data: param\r\n        });\r\n    };\r\n\r\n    LGWebSocket.prototype.onGetInputs = function() {\r\n        return [[\"in\", 0]];\r\n    };\r\n\r\n    LGWebSocket.prototype.onGetOutputs = function() {\r\n        return [[\"out\", 0]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"network/websocket\", LGWebSocket);\r\n\r\n    //It is like a websocket but using the SillyServer.js server that bounces packets back to all clients connected:\r\n    //For more information: https://github.com/jagenjo/SillyServer.js\r\n\r\n    function LGSillyClient() {\r\n        //this.size = [60,20];\r\n        this.room_widget = this.addWidget(\r\n            \"text\",\r\n            \"Room\",\r\n            \"lgraph\",\r\n            this.setRoom.bind(this)\r\n        );\r\n        this.addWidget(\r\n            \"button\",\r\n            \"Reconnect\",\r\n            null,\r\n            this.connectSocket.bind(this)\r\n        );\r\n\r\n        this.addInput(\"send\", LiteGraph.ACTION);\r\n        this.addOutput(\"received\", LiteGraph.EVENT);\r\n        this.addInput(\"in\", 0);\r\n        this.addOutput(\"out\", 0);\r\n        this.properties = {\r\n            url: \"tamats.com:55000\",\r\n            room: \"lgraph\",\r\n            only_send_changes: true\r\n        };\r\n\r\n        this._server = null;\r\n        this.connectSocket();\r\n        this._last_sent_data = [];\r\n        this._last_received_data = [];\r\n\r\n\t\tif(typeof(SillyClient) == \"undefined\")\r\n\t\t\tconsole.warn(\"remember to add SillyClient.js to your project: https://tamats.com/projects/sillyserver/src/sillyclient.js\");\r\n    }\r\n\r\n    LGSillyClient.title = \"SillyClient\";\r\n    LGSillyClient.desc = \"Connects to SillyServer to broadcast messages\";\r\n\r\n    LGSillyClient.prototype.onPropertyChanged = function(name, value) {\r\n        if (name == \"room\") {\r\n            this.room_widget.value = value;\r\n        }\r\n        this.connectSocket();\r\n    };\r\n\r\n    LGSillyClient.prototype.setRoom = function(room_name) {\r\n        this.properties.room = room_name;\r\n        this.room_widget.value = room_name;\r\n        this.connectSocket();\r\n    };\r\n\r\n    //force label names\r\n    LGSillyClient.prototype.onDrawForeground = function() {\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var slot = this.inputs[i];\r\n            slot.label = \"in_\" + i;\r\n        }\r\n        for (var i = 1; i < this.outputs.length; ++i) {\r\n            var slot = this.outputs[i];\r\n            slot.label = \"out_\" + i;\r\n        }\r\n    };\r\n\r\n    LGSillyClient.prototype.onExecute = function() {\r\n        if (!this._server || !this._server.is_connected) {\r\n            return;\r\n        }\r\n\r\n        var only_send_changes = this.properties.only_send_changes;\r\n\r\n        for (var i = 1; i < this.inputs.length; ++i) {\r\n            var data = this.getInputData(i);\r\n\t\t\tvar prev_data = this._last_sent_data[i];\r\n            if (data != null) {\r\n                if (only_send_changes)\r\n\t\t\t\t{\t\r\n\t\t\t\t\tvar is_equal = true;\r\n\t\t\t\t\tif( data && data.length && prev_data && prev_data.length == data.length && data.constructor !== String)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfor(var j = 0; j < data.length; ++j)\r\n\t\t\t\t\t\t\tif( prev_data[j] != data[j] )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tis_equal = false;\r\n\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if(this._last_sent_data[i] != data)\r\n\t\t\t\t\t\tis_equal = false;\r\n\t\t\t\t\tif(is_equal)\r\n\t\t\t\t\t\t\tcontinue;\r\n                }\r\n                this._server.sendMessage({ type: 0, channel: i, data: data });\r\n\t\t\t\tif( data.length && data.constructor !== String )\r\n\t\t\t\t{\r\n\t\t\t\t\tif( this._last_sent_data[i] )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis._last_sent_data[i].length = data.length;\r\n\t\t\t\t\t\tfor(var j = 0; j < data.length; ++j)\r\n\t\t\t\t\t\t\tthis._last_sent_data[i][j] = data[j];\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse //create\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif(data.constructor === Array)\r\n\t\t\t\t\t\t\tthis._last_sent_data[i] = data.concat();\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\tthis._last_sent_data[i] = new data.constructor( data );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t                this._last_sent_data[i] = data; //should be cloned\r\n            }\r\n        }\r\n\r\n        for (var i = 1; i < this.outputs.length; ++i) {\r\n            this.setOutputData(i, this._last_received_data[i]);\r\n        }\r\n\r\n        if (this.boxcolor == \"#AFA\") {\r\n            this.boxcolor = \"#6C6\";\r\n        }\r\n    };\r\n\r\n    LGSillyClient.prototype.connectSocket = function() {\r\n        var that = this;\r\n        if (typeof SillyClient == \"undefined\") {\r\n            if (!this._error) {\r\n                console.error(\r\n                    \"SillyClient node cannot be used, you must include SillyServer.js\"\r\n                );\r\n            }\r\n            this._error = true;\r\n            return;\r\n        }\r\n\r\n        this._server = new SillyClient();\r\n        this._server.on_ready = function() {\r\n            console.log(\"ready\");\r\n            that.boxcolor = \"#6C6\";\r\n        };\r\n        this._server.on_message = function(id, msg) {\r\n            var data = null;\r\n            try {\r\n                data = JSON.parse(msg);\r\n            } catch (err) {\r\n                return;\r\n            }\r\n\r\n            if (data.type == 1) {\r\n                //EVENT slot\r\n                if (\r\n                    data.data.object_class &&\r\n                    LiteGraph[data.data.object_class]\r\n                ) {\r\n                    var obj = null;\r\n                    try {\r\n                        obj = new LiteGraph[data.data.object_class](data.data);\r\n                        that.triggerSlot(0, obj);\r\n                    } catch (err) {\r\n                        return;\r\n                    }\r\n                } else {\r\n                    that.triggerSlot(0, data.data);\r\n                }\r\n            } //for FLOW slots\r\n            else {\r\n                that._last_received_data[data.channel || 0] = data.data;\r\n            }\r\n            that.boxcolor = \"#AFA\";\r\n        };\r\n        this._server.on_error = function(e) {\r\n            console.log(\"couldnt connect to websocket\");\r\n            that.boxcolor = \"#E88\";\r\n        };\r\n        this._server.on_close = function(e) {\r\n            console.log(\"connection closed\");\r\n            that.boxcolor = \"#000\";\r\n        };\r\n\r\n        if (this.properties.url && this.properties.room) {\r\n            try {\r\n                this._server.connect(this.properties.url, this.properties.room);\r\n            } catch (err) {\r\n                console.error(\"SillyServer error: \" + err);\r\n                this._server = null;\r\n                return;\r\n            }\r\n            this._final_url = this.properties.url + \"/\" + this.properties.room;\r\n        }\r\n    };\r\n\r\n    LGSillyClient.prototype.send = function(data) {\r\n        if (!this._server || !this._server.is_connected) {\r\n            return;\r\n        }\r\n        this._server.sendMessage({ type: 1, data: data });\r\n    };\r\n\r\n    LGSillyClient.prototype.onAction = function(action, param) {\r\n        if (!this._server || !this._server.is_connected) {\r\n            return;\r\n        }\r\n        this._server.sendMessage({ type: 1, action: action, data: param });\r\n    };\r\n\r\n    LGSillyClient.prototype.onGetInputs = function() {\r\n        return [[\"in\", 0]];\r\n    };\r\n\r\n    LGSillyClient.prototype.onGetOutputs = function() {\r\n        return [[\"out\", 0]];\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"network/sillyclient\", LGSillyClient);\r\n\r\n//HTTP Request\r\nfunction HTTPRequestNode() {\r\n\tvar that = this;\r\n\tthis.addInput(\"request\", LiteGraph.ACTION);\r\n\tthis.addInput(\"url\", \"string\");\r\n\tthis.addProperty(\"url\", \"\");\r\n\tthis.addOutput(\"ready\", LiteGraph.EVENT);\r\n    this.addOutput(\"data\", \"string\");\r\n\tthis.addWidget(\"button\", \"Fetch\", null, this.fetch.bind(this));\r\n\tthis._data = null;\r\n\tthis._fetching = null;\r\n}\r\n\r\nHTTPRequestNode.title = \"HTTP Request\";\r\nHTTPRequestNode.desc = \"Fetch data through HTTP\";\r\n\r\nHTTPRequestNode.prototype.fetch = function()\r\n{\r\n\tvar url = this.properties.url;\r\n\tif(!url)\r\n\t\treturn;\r\n\r\n\tthis.boxcolor = \"#FF0\";\r\n\tvar that = this;\r\n\tthis._fetching = fetch(url)\r\n\t.then(resp=>{\r\n\t\tif(!resp.ok)\r\n\t\t{\r\n\t\t\tthis.boxcolor = \"#F00\";\r\n\t\t\tthat.trigger(\"error\");\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.boxcolor = \"#0F0\";\r\n\t\t\treturn resp.text();\r\n\t\t}\r\n\t})\r\n\t.then(data=>{\r\n\t\tthat._data = data;\r\n\t\tthat._fetching = null;\r\n\t\tthat.trigger(\"ready\");\r\n\t});\r\n}\r\n\r\nHTTPRequestNode.prototype.onAction = function(evt)\r\n{\r\n\tif(evt == \"request\")\r\n\t\tthis.fetch();\r\n}\r\n\r\nHTTPRequestNode.prototype.onExecute = function() {\r\n\tthis.setOutputData(1, this._data);\r\n};\r\n\r\nHTTPRequestNode.prototype.onGetOutputs = function() {\r\n\treturn [[\"error\",LiteGraph.EVENT]];\r\n}\r\n\r\nLiteGraph.registerNodeType(\"network/httprequest\", HTTPRequestNode);\r\n\r\n\r\n\t\r\n})(this);\r\n"
  },
  {
    "path": "src/nodes/others.js",
    "content": "(function(global) {\n    var LiteGraph = global.LiteGraph;\n    \n    /* in types :: run in console :: var s=\"\"; LiteGraph.slot_types_in.forEach(function(el){s+=el+\"\\n\";}); console.log(s); */\n    \n    if(typeof LiteGraph.slot_types_default_in == \"undefined\") LiteGraph.slot_types_default_in = {}; //[];\n    LiteGraph.slot_types_default_in[\"_event_\"] = \"widget/button\";\n    LiteGraph.slot_types_default_in[\"array\"] = \"basic/array\";\n    LiteGraph.slot_types_default_in[\"boolean\"] = \"basic/boolean\";\n    LiteGraph.slot_types_default_in[\"number\"] = \"widget/number\";\n    LiteGraph.slot_types_default_in[\"object\"] = \"basic/data\";\n    LiteGraph.slot_types_default_in[\"string\"] = [\"basic/string\",\"string/concatenate\"];\n    LiteGraph.slot_types_default_in[\"vec2\"] = \"math3d/xy-to-vec2\";\n    LiteGraph.slot_types_default_in[\"vec3\"] = \"math3d/xyz-to-vec3\";\n    LiteGraph.slot_types_default_in[\"vec4\"] = \"math3d/xyzw-to-vec4\";\n    \n    /* out types :: run in console :: var s=\"\"; LiteGraph.slot_types_out.forEach(function(el){s+=el+\"\\n\";}); console.log(s); */\n    if(typeof LiteGraph.slot_types_default_out == \"undefined\") LiteGraph.slot_types_default_out = {};\n    LiteGraph.slot_types_default_out[\"_event_\"] = [\"logic/IF\",\"events/sequencer\",\"events/log\",\"events/counter\"];\n    LiteGraph.slot_types_default_out[\"array\"] = [\"basic/watch\",\"basic/set_array\",\"basic/array[]\"];\n    LiteGraph.slot_types_default_out[\"boolean\"] = [\"logic/IF\",\"basic/watch\",\"math/branch\",\"math/gate\"];\n    LiteGraph.slot_types_default_out[\"number\"] = [\"basic/watch\"\n\t\t\t\t\t\t\t\t\t\t\t\t  ,{node:\"math/operation\",properties:{OP:\"*\"},title:\"A*B\"}\n\t\t\t\t\t\t\t\t\t\t\t\t  ,{node:\"math/operation\",properties:{OP:\"/\"},title:\"A/B\"}\n\t\t\t\t\t\t\t\t\t\t\t\t  ,{node:\"math/operation\",properties:{OP:\"+\"},title:\"A+B\"}\n\t\t\t\t\t\t\t\t\t\t\t\t  ,{node:\"math/operation\",properties:{OP:\"-\"},title:\"A-B\"}\n\t\t\t\t\t\t\t\t\t\t\t\t  ,{node:\"math/compare\",outputs:[[\"A==B\", \"boolean\"]],title:\"A==B\"}\n\t\t\t\t\t\t\t\t\t\t\t\t  ,{node:\"math/compare\",outputs:[[\"A>B\", \"boolean\"]],title:\"A>B\"}\n\t\t\t\t\t\t\t\t\t\t\t\t  ,{node:\"math/compare\",outputs:[[\"A<B\", \"boolean\"]],title:\"A<B\"}\n\t\t\t\t\t\t\t\t\t\t\t\t];\n    LiteGraph.slot_types_default_out[\"object\"] = [\"basic/object_property\",\"basic/keys\",[\"string/toString\",\"basic/watch\"]];\n    LiteGraph.slot_types_default_out[\"string\"] = [\"basic/watch\",\"string/compare\",\"string/concatenate\",\"string/contains\"];\n    LiteGraph.slot_types_default_out[\"vec2\"] = \"math3d/vec2-to-xy\";\n    LiteGraph.slot_types_default_out[\"vec3\"] = \"math3d/vec3-to-xyz\";\n    LiteGraph.slot_types_default_out[\"vec4\"] = \"math3d/vec4-to-xyzw\";\n    \n})(this);"
  },
  {
    "path": "src/nodes/strings.js",
    "content": "//basic nodes\r\n(function(global) {\r\n    var LiteGraph = global.LiteGraph;\r\n\r\n    function toString(a) {\r\n\t\tif(a && a.constructor === Object)\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\treturn JSON.stringify(a);\r\n\t\t\t}\r\n\t\t\tcatch (err)\r\n\t\t\t{\r\n\t\t\t\treturn String(a);\r\n\t\t\t}\r\n\t\t}\r\n        return String(a);\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\"string/toString\", toString, [\"\"], \"string\");\r\n\r\n    function compare(a, b) {\r\n        return a == b;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/compare\",\r\n        compare,\r\n        [\"string\", \"string\"],\r\n        \"boolean\"\r\n    );\r\n\r\n    function concatenate(a, b) {\r\n        if (a === undefined) {\r\n            return b;\r\n        }\r\n        if (b === undefined) {\r\n            return a;\r\n        }\r\n        return a + b;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/concatenate\",\r\n        concatenate,\r\n        [\"string\", \"string\"],\r\n        \"string\"\r\n    );\r\n\r\n    function contains(a, b) {\r\n        if (a === undefined || b === undefined) {\r\n            return false;\r\n        }\r\n        return a.indexOf(b) != -1;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/contains\",\r\n        contains,\r\n        [\"string\", \"string\"],\r\n        \"boolean\"\r\n    );\r\n\r\n    function toUpperCase(a) {\r\n        if (a != null && a.constructor === String) {\r\n            return a.toUpperCase();\r\n        }\r\n        return a;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/toUpperCase\",\r\n        toUpperCase,\r\n        [\"string\"],\r\n        \"string\"\r\n    );\r\n\r\n    function split(str, separator) {\r\n\t\tif(separator == null)\r\n\t\t\tseparator = this.properties.separator;\r\n        if (str == null )\r\n\t        return [];\r\n\t\tif( str.constructor === String )\r\n\t\t\treturn str.split(separator || \" \");\r\n\t\telse if( str.constructor === Array )\r\n\t\t{\r\n\t\t\tvar r = [];\r\n\t\t\tfor(var i = 0; i < str.length; ++i){\r\n                if (typeof str[i] == \"string\")\r\n\t\t\t\t    r[i] = str[i].split(separator || \" \");\r\n            }\r\n\t\t\treturn r;\r\n\t\t}\r\n        return null;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/split\",\r\n        split,\r\n        [\"string,array\", \"string\"],\r\n        \"array\",\r\n\t\t{ separator: \",\" }\r\n    );\r\n\r\n    function toFixed(a) {\r\n        if (a != null && a.constructor === Number) {\r\n            return a.toFixed(this.properties.precision);\r\n        }\r\n        return a;\r\n    }\r\n\r\n    LiteGraph.wrapFunctionAsNode(\r\n        \"string/toFixed\",\r\n        toFixed,\r\n        [\"number\"],\r\n        \"string\",\r\n        { precision: 0 }\r\n    );\r\n\r\n\r\n    function StringToTable() {\r\n        this.addInput(\"\", \"string\");\r\n        this.addOutput(\"table\", \"table\");\r\n        this.addOutput(\"rows\", \"number\");\r\n        this.addProperty(\"value\", \"\");\r\n        this.addProperty(\"separator\", \",\");\r\n\t\tthis._table = null;\r\n    }\r\n\r\n    StringToTable.title = \"toTable\";\r\n    StringToTable.desc = \"Splits a string to table\";\r\n\r\n    StringToTable.prototype.onExecute = function() {\r\n        var input = this.getInputData(0);\r\n\t\tif(!input)\r\n\t\t\treturn;\r\n\t\tvar separator = this.properties.separator || \",\";\r\n\t\tif(input != this._str || separator != this._last_separator )\r\n\t\t{\r\n\t\t\tthis._last_separator = separator;\r\n\t\t\tthis._str = input;\r\n\t\t\tthis._table = input.split(\"\\n\").map(function(a){ return a.trim().split(separator)});\r\n\t\t}\r\n        this.setOutputData(0, this._table );\r\n        this.setOutputData(1, this._table ? this._table.length : 0 );\r\n    };\r\n\r\n    LiteGraph.registerNodeType(\"string/toTable\", StringToTable);\r\n\r\n})(this);\r\n"
  },
  {
    "path": "style.css",
    "content": "body { background-color: #DDD; }\r\nh1 {\r\n\tmargin: 0;\r\n}\r\n\r\n#wrap {\r\n\tmargin: auto;\r\n\twidth: 800px;\r\n\tmin-height: 400px;\r\n\tpadding: 1em;\r\n\tbackground-color: white;\r\n\tbox-shadow: 0 0 2px #333;\r\n}\r\n\r\n"
  },
  {
    "path": "utils/build.sh",
    "content": "cd \"$(dirname \"$0\")\"\npython builder.py deploy_files.txt -o ../build/litegraph.min.js -o2 ../build/litegraph.js\npython builder.py deploy_files_mini.txt -o ../build/litegraph_mini.min.js -o2 ../build/litegraph_mini.js\npython builder.py deploy_files_core.txt -o ../build/litegraph.core.min.js -o2 ../build/litegraph.core.js\nchmod a+rw ../build/* \n"
  },
  {
    "path": "utils/builder.py",
    "content": "#!/usr/bin/python\n\nimport re, os, sys, time, tempfile, shutil\nimport argparse\nfrom datetime import date\n\nroot_path = \"./\"\n\n#compiler_path = \"../node_modules/google-closure-compiler/cli.js\"\n#compiler_path = \"../node_modules/google-closure-compiler-java/compiler.jar\"\n#compiler_path = \"google-clouse-compiler\"\n\n#compiler_path = \"java -jar ../node_modules/google-closure-compiler-java/compiler.jar --js %s --js_output_file %s\"\ncompiler_path = \"../node_modules/google-closure-compiler/cli.js --js %s --js_output_file %s\"\n\n#arguments\nparser = argparse.ArgumentParser(description='Deploy a JS app creating a minifyed version checking for errors.')\nparser.add_argument('input_file',\n                   help='the path to the file with a list of all the JS files')\n\nparser.add_argument('-o', dest='output_file', action='store',\n                   default=None,\n                   help='Specify an output for the minifyed version')\n\nparser.add_argument('-o2', dest='fullcode_output_file', action='store',\n                   default=None,\n                   help='Specify an output for the full code version')\n\n#parser.add_argument('output_file',\n#                   help='the filename where to save the min version')\n\nparser.add_argument('--all', dest='all_files', action='store_const',\n                   const=True, default=False,\n                   help='Compile all JS files individually first.')\nparser.add_argument('--nomin', dest='no_minify', action='store_const',\n                   const=True, default=False,\n                   help='Do not minify the JS file')\n\nargs = parser.parse_args()\n\ncheck_files_individually = args.all_files\noutput_file = args.output_file\nfullcode_output_file = args.fullcode_output_file\nno_minify = args.no_minify\n\nroot_path = \"./\" + os.path.dirname(args.input_file) + \"/\"\nsys.stderr.write(\" + Root folder: \" + root_path + \"\\n\")\n\ndef packJSCode(files):\n    f1, fullcode_path = tempfile.mkstemp() #create temporary file\n    data = \"//packer version\\n\\n\"\n\n    for filename in files:\n        filename = filename.strip()\n        if len(filename) == 0 or filename[0] == \"#\":\n            continue\n        sys.stderr.write(\" + Processing... \" + filename + \" \" )\n        src_file = root_path + filename\n        if os.path.exists(src_file) == False:\n            sys.stderr.write('\\033[91m'+\"JS File not found\"+'\\033[0m\\n')\n            continue\n        data += open(src_file).read() + \"\\n\"\n        if check_files_individually:\n            os.system( compiler_path % ( src_file, \"temp.js\") )\n            #os.system( \"java -jar %s --js %s --js_output_file %s\" % (compiler_path, src_file, \"temp.js\") )\n        sys.stderr.write('\\033[92m' + \"OK\\n\" + '\\033[0m')\n\n    os.write(f1,data)\n    os.close(f1)\n\n    #print \" + Compiling all...\"\n    #os.system(\"java -jar %s --js %s --js_output_file %s\" % (compiler_path, fullcode_path, output_file) )\n    #print \" * Done\"\n    return fullcode_path\n\ndef compileAndMinify(input_path, output_path):\n    print \" + Compiling and minifying...\"\n    if output_path != None:\n        os.system( compiler_path % ( input_path, output_path) )\n        sys.stderr.write(\" * Stored in \" + output_path + \"\\n\");\n    else:\n        os.system( compiler_path % ( input_path ) )\n\n#load project info\nif os.path.exists(args.input_file) == False:\n    sys.stderr.write(\"\\033[91m Error, input file not found: \" + args.input_file + \"\\033[0m\\n\")\n    exit(0)\n\njs_files = open(args.input_file).read().splitlines()\n\nfullcode_path = packJSCode(js_files)\n\nif fullcode_output_file != None:\n    shutil.copy2(fullcode_path, fullcode_output_file)\n    sys.stderr.write(\" * Fullcode Stored in \" + fullcode_output_file + \"\\n\");\n\nif not no_minify:\n    compileAndMinify( fullcode_path, output_file )\n"
  },
  {
    "path": "utils/deploy_files.txt",
    "content": "../src/litegraph.js\r\n../src/nodes/base.js\r\n../src/nodes/events.js\r\n../src/nodes/interface.js\r\n../src/nodes/input.js\r\n../src/nodes/math.js\r\n../src/nodes/math3d.js\r\n../src/nodes/strings.js\r\n../src/nodes/logic.js\r\n../src/nodes/graphics.js\r\n../src/nodes/gltextures.js\r\n../src/nodes/glshaders.js\r\n../src/nodes/geometry.js\r\n../src/nodes/glfx.js\r\n../src/nodes/midi.js\r\n../src/nodes/audio.js\r\n../src/nodes/network.js\r\n"
  },
  {
    "path": "utils/deploy_files_core.txt",
    "content": "../src/litegraph.js\n"
  },
  {
    "path": "utils/deploy_files_mini.txt",
    "content": "../src/litegraph.js\r\n../src/nodes/base.js\r\n../src/nodes/events.js\r\n../src/nodes/input.js\r\n../src/nodes/math.js\r\n../src/nodes/strings.js\r\n../src/nodes/logic.js\r\n../src/nodes/network.js\r\n"
  },
  {
    "path": "utils/generate_doc.sh",
    "content": "#!/bin/bash\n\n# For migration to JSDoc (YUIDoc was deprecated in 2014): https://github.com/pnstickne/yuidoc-to-jsdoc\n\ncd \"$(dirname \"$0\")\" || exit\nrm -rf ../docs/*\n../node_modules/.bin/yuidoc ../src -o ../docs\n"
  },
  {
    "path": "utils/pack.sh",
    "content": "cd \"$(dirname \"$0\")\"\npython builder.py deploy_files.txt -o ../build/litegraph.min.js -o2 ../build/litegraph.js --nomin\npython builder.py deploy_files_mini.txt -o ../build/litegraph_mini.min.js -o2 ../build/litegraph_mini.js --nomin\nchmod a+rw ../build/* \n"
  },
  {
    "path": "utils/server.js",
    "content": "const express = require('express')\nconst app = express()\n\napp.use('/css', express.static('css'))\napp.use('/src', express.static('src'))\napp.use('/external', express.static('external'))\napp.use('/editor', express.static('editor'))\napp.use('/', express.static('editor'))\n\napp.listen(8000, () => console.log('Example app listening on http://127.0.0.1:8000'))\n"
  },
  {
    "path": "utils/temp.js",
    "content": "LiteGraph.registerNodeType(\"color/palette\",{title:\"Palette\",desc:\"Generates a color\",inputs:[[\"f\",\"number\"]],outputs:[[\"Color\",\"color\"]],properties:{colorA:\"#444444\",colorB:\"#44AAFF\",colorC:\"#44FFAA\",colorD:\"#FFFFFF\"},onExecute:function(){var a=[];null!=this.properties.colorA&&a.push(hex2num(this.properties.colorA));null!=this.properties.colorB&&a.push(hex2num(this.properties.colorB));null!=this.properties.colorC&&a.push(hex2num(this.properties.colorC));null!=this.properties.colorD&&a.push(hex2num(this.properties.colorD));\nvar b=this.getInputData(0);null==b&&(b=0.5);1<b?b=1:0>b&&(b=0);if(0!=a.length){var c=[0,0,0];if(0==b)c=a[0];else if(1==b)c=a[a.length-1];else{var d=(a.length-1)*b,b=a[Math.floor(d)],a=a[Math.floor(d)+1],d=d-Math.floor(d);c[0]=b[0]*(1-d)+a[0]*d;c[1]=b[1]*(1-d)+a[1]*d;c[2]=b[2]*(1-d)+a[2]*d}for(var e in c)c[e]/=255;this.boxcolor=colorToString(c);this.setOutputData(0,c)}}});\nLiteGraph.registerNodeType(\"graphics/frame\",{title:\"Frame\",desc:\"Frame viewerew\",inputs:[[\"\",\"image\"]],size:[200,200],widgets:[{name:\"resize\",text:\"Resize box\",type:\"button\"},{name:\"view\",text:\"View Image\",type:\"button\"}],onDrawBackground:function(a){this.frame&&a.drawImage(this.frame,0,0,this.size[0],this.size[1])},onExecute:function(){this.frame=this.getInputData(0);this.setDirtyCanvas(!0)},onWidget:function(a,b){if(\"resize\"==b.name&&this.frame){var c=this.frame.width,d=this.frame.height;c||null==\nthis.frame.videoWidth||(c=this.frame.videoWidth,d=this.frame.videoHeight);c&&d&&(this.size=[c,d]);this.setDirtyCanvas(!0,!0)}else\"view\"==b.name&&this.show()},show:function(){showElement&&this.frame&&showElement(this.frame)}});\nLiteGraph.registerNodeType(\"visualization/graph\",{desc:\"Shows a graph of the inputs\",inputs:[[\"\",0],[\"\",0],[\"\",0],[\"\",0]],size:[200,200],properties:{min:-1,max:1,bgColor:\"#000\"},onDrawBackground:function(a){var b=[\"#FFF\",\"#FAA\",\"#AFA\",\"#AAF\"];null!=this.properties.bgColor&&\"\"!=this.properties.bgColor&&(a.fillStyle=\"#000\",a.fillRect(2,2,this.size[0]-4,this.size[1]-4));if(this.data){var c=this.properties.min,d=this.properties.max,e;for(e in this.data){var h=this.data[e];if(h&&null!=this.getInputInfo(e)){a.strokeStyle=\nb[e];a.beginPath();for(var k=h.length/this.size[0],g=0;g<h.length;g+=k){var f=h[Math.floor(g)],f=(f-c)/(d-c);1<f?f=1:0>f&&(f=0);0==g?a.moveTo(g/k,this.size[1]-5-(this.size[1]-10)*f):a.lineTo(g/k,this.size[1]-5-(this.size[1]-10)*f)}a.stroke()}}}},onExecute:function(){this.data||(this.data=[]);for(var a in this.inputs){var b=this.getInputData(a);\"number\"==typeof b?(b=b?b:0,this.data[a]||(this.data[a]=[]),this.data[a].push(b),this.data[a].length>this.size[1]-4&&(this.data[a]=this.data[a].slice(1,this.data[a].length))):\nthis.data[a]=b}this.data.length&&this.setDirtyCanvas(!0)}});\nLiteGraph.registerNodeType(\"graphics/supergraph\",{title:\"Supergraph\",desc:\"Shows a nice circular graph\",inputs:[[\"x\",\"number\"],[\"y\",\"number\"],[\"c\",\"color\"]],outputs:[[\"\",\"image\"]],widgets:[{name:\"clear_alpha\",text:\"Clear Alpha\",type:\"minibutton\"},{name:\"clear_color\",text:\"Clear color\",type:\"minibutton\"}],properties:{size:256,bgcolor:\"#000\",lineWidth:1},bgcolor:\"#000\",flags:{allow_fastrender:!0},onLoad:function(){this.createCanvas()},createCanvas:function(){this.canvas=document.createElement(\"canvas\");\nthis.canvas.width=this.properties.size;this.canvas.height=this.properties.size;this.oldpos=null;this.clearCanvas(!0)},onExecute:function(){var a=this.getInputData(0),b=this.getInputData(1),c=this.getInputData(2);if(null!=a||null!=b){a||(a=0);b||(b=0);var a=0.95*a,b=0.95*b,d=this.properties.size;d==this.canvas.width&&d==this.canvas.height||this.createCanvas();if(this.oldpos){var e=this.canvas.getContext(\"2d\");null==c?c=\"rgba(255,255,255,0.5)\":\"object\"==typeof c&&(c=colorToString(c));e.strokeStyle=\nc;e.beginPath();e.moveTo(this.oldpos[0],this.oldpos[1]);this.oldpos=[(0.5*a+0.5)*d,(0.5*b+0.5)*d];e.lineTo(this.oldpos[0],this.oldpos[1]);e.stroke();this.canvas.dirty=!0;this.setOutputData(0,this.canvas)}else this.oldpos=[(0.5*a+0.5)*d,(0.5*b+0.5)*d]}},clearCanvas:function(a){var b=this.canvas.getContext(\"2d\");a?(b.clearRect(0,0,this.canvas.width,this.canvas.height),this.trace(\"Clearing alpha\")):(b.fillStyle=this.properties.bgcolor,b.fillRect(0,0,this.canvas.width,this.canvas.height))},onWidget:function(a,\nb){\"clear_color\"==b.name?this.clearCanvas(!1):\"clear_alpha\"==b.name&&this.clearCanvas(!0)},onPropertyChange:function(a,b){if(\"size\"==a)this.properties.size=parseInt(b),this.createCanvas();else if(\"bgcolor\"==a)this.properties.bgcolor=b,this.createCanvas();else if(\"lineWidth\"==a)this.properties.lineWidth=parseInt(b),this.canvas.getContext(\"2d\").lineWidth=this.properties.lineWidth;else return!1;return!0}});\nLiteGraph.registerNodeType(\"graphics/imagefade\",{title:\"Image fade\",desc:\"Fades between images\",inputs:[[\"img1\",\"image\"],[\"img2\",\"image\"],[\"fade\",\"number\"]],outputs:[[\"\",\"image\"]],properties:{fade:0.5,width:512,height:512},widgets:[{name:\"resizeA\",text:\"Resize to A\",type:\"button\"},{name:\"resizeB\",text:\"Resize to B\",type:\"button\"}],onLoad:function(){this.createCanvas();var a=this.canvas.getContext(\"2d\");a.fillStyle=\"#000\";a.fillRect(0,0,this.properties.width,this.properties.height)},createCanvas:function(){this.canvas=\ndocument.createElement(\"canvas\");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height},onExecute:function(){var a=this.canvas.getContext(\"2d\");this.canvas.width=this.canvas.width;var b=this.getInputData(0);null!=b&&a.drawImage(b,0,0,this.canvas.width,this.canvas.height);b=this.getInputData(2);null==b?b=this.properties.fade:this.properties.fade=b;a.globalAlpha=b;b=this.getInputData(1);null!=b&&a.drawImage(b,0,0,this.canvas.width,this.canvas.height);a.globalAlpha=1;this.setOutputData(0,\nthis.canvas);this.setDirtyCanvas(!0)}});\nLiteGraph.registerNodeType(\"graphics/image\",{title:\"Image\",desc:\"Image loader\",inputs:[],outputs:[[\"frame\",\"image\"]],properties:{url:\"\"},widgets:[{name:\"load\",text:\"Load\",type:\"button\"}],onLoad:function(){\"\"!=this.properties.url&&null==this.img&&this.loadImage(this.properties.url)},onStart:function(){},onExecute:function(){this.img||(this.boxcolor=\"#000\");this.img&&this.img.width?this.setOutputData(0,this.img):this.setOutputData(0,null);this.img.dirty&&(this.img.dirty=!1)},onPropertyChange:function(a,\nb){this.properties[a]=b;\"url\"==a&&\"\"!=b&&this.loadImage(b);return!0},loadImage:function(a){if(\"\"==a)this.img=null;else{this.trace(\"loading image...\");this.img=document.createElement(\"img\");this.img.src=\"miniproxy.php?url=\"+a;this.boxcolor=\"#F95\";var b=this;this.img.onload=function(){b.trace(\"Image loaded, size: \"+b.img.width+\"x\"+b.img.height);this.dirty=!0;b.boxcolor=\"#9F9\";b.setDirtyCanvas(!0)}}},onWidget:function(a,b){\"load\"==b.name&&this.loadImage(this.properties.url)}});\nLiteGraph.registerNodeType(\"graphics/cropImage\",{title:\"Crop\",desc:\"Crop Image\",inputs:[[\"\",\"image\"]],outputs:[[\"\",\"image\"]],properties:{width:256,height:256,x:0,y:0,scale:1},size:[50,20],onLoad:function(){this.createCanvas()},createCanvas:function(){this.canvas=document.createElement(\"canvas\");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height},onExecute:function(){var a=this.getInputData(0);a&&(a.width?(this.canvas.getContext(\"2d\").drawImage(a,-this.properties.x,-this.properties.y,\na.width*this.properties.scale,a.height*this.properties.scale),this.setOutputData(0,this.canvas)):this.setOutputData(0,null))},onPropertyChange:function(a,b){this.properties[a]=b;\"scale\"==a?(this.properties[a]=parseFloat(b),0==this.properties[a]&&(this.trace(\"Error in scale\"),this.properties[a]=1)):this.properties[a]=parseInt(b);this.createCanvas();return!0}});\nLiteGraph.registerNodeType(\"graphics/video\",{title:\"Video\",desc:\"Video playback\",inputs:[[\"t\",\"number\"]],outputs:[[\"frame\",\"image\"],[\"t\",\"number\"],[\"d\",\"number\"]],properties:{url:\"\"},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\"}],onClick:function(a){if(this.video&&20>distance([a.canvasX,a.canvasY],[this.pos[0]+55,this.pos[1]+40]))return this.play(),!0},onKeyDown:function(a){32==\na.keyCode&&this.playPause()},onLoad:function(){\"\"!=this.properties.url&&this.loadVideo(this.properties.url)},play:function(){this.video&&(this.trace(\"Video playing\"),this.video.play())},playPause:function(){this.video&&(this.video.paused?this.play():this.pause())},stop:function(){this.video&&(this.trace(\"Video stopped\"),this.video.pause(),this.video.currentTime=0)},pause:function(){this.video&&(this.trace(\"Video paused\"),this.video.pause())},onExecute:function(){if(this.video){var a=this.getInputData(0);\na&&0<=a&&1>=a&&(this.video.currentTime=a*this.video.duration,this.video.pause());this.video.dirty=!0;this.setOutputData(0,this.video);this.setOutputData(1,this.video.currentTime);this.setOutputData(2,this.video.duration);this.setDirtyCanvas(!0)}},onStart:function(){},onStop:function(){this.pause()},loadVideo:function(a){this.video=document.createElement(\"video\");a?this.video.src=a:(this.video.src=\"modules/data/video.webm\",this.properties.url=this.video.src);this.video.type=\"type=video/mp4\";this.video.muted=\n!0;this.video.autoplay=!1;var b=this;this.video.addEventListener(\"loadedmetadata\",function(a){b.trace(\"Duration: \"+b.video.duration+\" seconds\");b.trace(\"Size: \"+b.video.videoWidth+\",\"+b.video.videoHeight);b.setDirtyCanvas(!0);this.width=this.videoWidth;this.height=this.videoHeight});this.video.addEventListener(\"progress\",function(a){});this.video.addEventListener(\"error\",function(a){b.trace(\"Error loading video: \"+this.src);if(this.error)switch(this.error.code){case this.error.MEDIA_ERR_ABORTED:b.trace(\"You stopped the video.\");\nbreak;case this.error.MEDIA_ERR_NETWORK:b.trace(\"Network error - please try again later.\");break;case this.error.MEDIA_ERR_DECODE:b.trace(\"Video is broken..\");break;case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:b.trace(\"Sorry, your browser can't play this video.\")}});this.video.addEventListener(\"ended\",function(a){b.trace(\"Ended.\");this.play()})},onPropertyChange:function(a,b){this.properties[a]=b;\"url\"==a&&\"\"!=b&&this.loadVideo(b);return!0},onWidget:function(a,b){\"demo\"==b.name?this.loadVideo():\"play\"==\nb.name&&this.video&&this.playPause();\"stop\"==b.name?this.stop():\"mute\"==b.name&&this.video&&(this.video.muted=!this.video.muted)}});\n"
  },
  {
    "path": "utils/test.sh",
    "content": "#!/bin/bash\n\nset -eo pipefail\ncd \"$(dirname \"$(readlink -f \"${BASH_SOURCE[0]}\")\")\"\n\nexport NVM_DIR=$HOME/.nvm\nsource \"$NVM_DIR/nvm.sh\"\n\n# This are versions 12, 14, 16, 18\nNODE_VERSIONS=(\"lts/erbium\" \"lts/fermium\" \"lts/gallium\" \"lts/hydrogen\")\n\nfor NODE_VERSION in \"${NODE_VERSIONS[@]}\"; do\n  nvm install \"$NODE_VERSION\"\n  nvm exec \"$NODE_VERSION\" npm install\n  nvm exec \"$NODE_VERSION\" npm test\ndone\n"
  }
]