[
  {
    "path": ".editorconfig",
    "content": "[*]\nindent_style = tab\nindent_size = 2\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish to Comfy registry\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n      - master\n    paths:\n      - \"pyproject.toml\"\n\npermissions:\n  issues: write\n\njobs:\n  publish-node:\n    name: Publish Custom Node to registry\n    runs-on: ubuntu-latest\n    if: ${{ github.repository_owner == 'niknah' }}\n    steps:\n      - name: Check out code\n        uses: actions/checkout@v4\n      - name: Publish Custom Node\n        uses: Comfy-Org/publish-node-action@v1\n        with:\n          ## Add your own personal access token to your Github Repository secrets and reference it here.\n          personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# example/links is made by example/run_example.sh\nexample/links\nnode_modules\n__pycache__\n"
  },
  {
    "path": "README.md",
    "content": "\nAdds circuit board connections, quick connections to ComfyUI\n\n![Example](imgs/CircuitBoardExample.webp)\n![Example](imgs/CreateSimple.gif)\n\n\n* To install, go to [ComfyUI manager](https://github.com/ltdrdata/ComfyUI-Manager) -> look up \"quick-connections\"\n* [Demo](https://niknah.github.io/quick-connections/quick_conn.html?nodebug=1)\n\n## How to use...\n\n\n* Don't block the output / input areas.\n* Give it some room.  If the connections have no room to move, it'll get confused.\n* There is no insert dot in the middle of the connection.  Drag the output to an empty spot, add a new node, and drag the output of the new node.  Sorry.\n* Can be disabled in options under \"Quick connections\", \"Circuit Board lines\"\n\n\n## Can be used with litegraph too.  See example folder(examples won't work in windows because it doesn't support symlinks, use WSL).\nTo run examples...\n```\nnpm install\npython -m http.server\n# visit in browser: http://localhost:8000/example/quick_conn.html\n```\n\n\n## Changelog\n\n* 2026-04-04 v1.0.29: Added adjustable numbers for line / node spacing\n* 2026-03-30 v1.0.27: Catch errors and continue, so we don't stop any other things that are supposed to happen [#31]\n* 2026-03-28 v1.0.26: Fix for show/hide links button not working [#31]\n* 2026-03-10 v1.0.25: Missing round corners some times.  Funny line when two nodes are close together.\n* 2025-08-29 v1.0.21: Changes to get subgraphs working.\n* 2025-08-26 v1.0.20: Disable quick-connections in subgraphs, not working.\n* 2025-08-23 v1.0.19: Possible crashes in subgraph node fix. https://github.com/niknah/quick-connections/issues/25\n* 2025-07-15 v1.0.17: Draw connections in subgraph mode.  https://github.com/niknah/quick-connections/issues/24\n* 2025-07-04 v1.0.16: Problem with the enable/disable toggle not working.  https://github.com/niknah/quick-connections/issues/19\n* 2024-11-03: It defaults to mostly 90 or 45 degree lines now.  This can be changed in the options back to the old way(connect any angle when nothing is blocking).\n"
  },
  {
    "path": "__init__.py",
    "content": "\n# Set the web directory, any .js file in that directory will be loaded by the frontend as a frontend extension\nWEB_DIRECTORY = \"./js\"\n\nNODE_CLASS_MAPPINGS = {\n}\n\n\n"
  },
  {
    "path": "docs/CircuitBoardLines.js",
    "content": "/* eslint max-classes-per-file: 0 */\n/* eslint no-tabs: 0 */\n/* eslint no-underscore-dangle:0 */\n/* eslint import/prefer-default-export:0 */\n/* eslint prefer-rest-params:0 */\n/* eslint curly:0 */\n/* eslint no-plusplus:0 */\n/* global LiteGraph */\n/* global LGraphCanvas */\n\n/**\n * @preserve\n * Fast, destructive implemetation of Liang-Barsky line clipping algorithm.\n * It clips a 2D segment by a rectangle.\n * @author Alexander Milevski <info@w8r.name>\n * @license MIT\n */\nconst EPSILON = 1e-6;\nconst INSIDE = 1;\nconst OUTSIDE = 0;\n\nfunction clipT(num, denom, c) {\n\t/* eslint-disable one-var,no-param-reassign,prefer-destructuring,operator-linebreak */\n\t/* eslint-disable one-var-declaration-per-line,nonblock-statement-body-position,curly */\n\tconst tE = c[0], tL = c[1];\n\tif (Math.abs(denom) < EPSILON)\n\t\treturn num < 0;\n\tconst t = num / denom;\n\tif (denom > 0) {\n\t\tif (t > tL)\n\t\t\treturn 0;\n\t\tif (t > tE)\n\t\t\tc[0] = t;\n\t} else {\n\t\tif (t < tE)\n\t\t\treturn 0;\n\t\tif (t < tL)\n\t\t\tc[1] = t;\n\t}\n\treturn 1;\n}\n/**\n * @param\t{Point} a\n * @param\t{Point} b\n * @param\t{BoundingBox} box [xmin, ymin, xmax, ymax]\n * @param\t{Point?} [da]\n * @param\t{Point?} [db]\n * @return {number}\n */\nfunction liangBarsky(a, b, box, da, db) {\n\t/* eslint-disable one-var,no-param-reassign,prefer-destructuring,operator-linebreak */\n\t/* eslint-disable one-var-declaration-per-line */\n\tconst x1 = a[0], y1 = a[1];\n\tconst x2 = b[0], y2 = b[1];\n\tconst dx = x2 - x1;\n\tconst dy = y2 - y1;\n\tif (da === undefined || db === undefined) {\n\t\tda = a;\n\t\tdb = b;\n\t} else {\n\t\tda[0] = a[0];\n\t\tda[1] = a[1];\n\t\tdb[0] = b[0];\n\t\tdb[1] = b[1];\n\t}\n\tif (Math.abs(dx) < EPSILON &&\n\t\tMath.abs(dy) < EPSILON &&\n\t\tx1 >= box[0] &&\n\t\tx1 <= box[2] &&\n\t\ty1 >= box[1] &&\n\t\ty1 <= box[3]) {\n\t\treturn INSIDE;\n\t}\n\tconst c = [0, 1];\n\tif (clipT(box[0] - x1, dx, c) &&\n\t\tclipT(x1 - box[2], -dx, c) &&\n\t\tclipT(box[1] - y1, dy, c) &&\n\t\tclipT(y1 - box[3], -dy, c)) {\n\t\tconst tE = c[0], tL = c[1];\n\t\tif (tL < 1) {\n\t\t\tdb[0] = x1 + tL * dx;\n\t\t\tdb[1] = y1 + tL * dy;\n\t\t}\n\t\tif (tE > 0) {\n\t\t\tda[0] += tE * dx;\n\t\t\tda[1] += tE * dy;\n\t\t}\n\t\treturn INSIDE;\n\t}\n\treturn OUTSIDE;\n}\n\nclass MapLinks {\n\tconstructor(canvas) {\n\t\tthis.canvas = canvas;\n\t\tthis.nodesByRight = [];\n\t\tthis.nodesById = [];\n\t\tthis.lastPathId = 10000000;\n\t\tthis.paths = [];\n\t\tthis.lineSpace = Math.floor(LiteGraph.NODE_SLOT_HEIGHT / 2);\n\t\tthis.maxDirectLineDistance = Number.MAX_SAFE_INTEGER;\n\t\tthis.debug = false;\n\t}\n\n\tisInsideNode(xy) {\n\t\tfor (let i = 0; i < this.nodesByRight.length; ++i) {\n\t\t\tconst nodeI = this.nodesByRight[i];\n\t\t\tif (nodeI.node.isPointInside(xy[0], xy[1])) {\n\t\t\t\treturn nodeI.node;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tfindClippedNode(outputXY, inputXY) {\n\t\tlet closestDistance = Number.MAX_SAFE_INTEGER;\n\t\tlet closest = null;\n\n\t\tfor (let i = 0; i < this.nodesByRight.length; ++i) {\n\t\t\tconst node = this.nodesByRight[i];\n\t\t\tconst clipA = [-1, -1]; // outputXY.slice();\n\t\t\tconst clipB = [-1, -1]; // inputXY.slice();\n\t\t\tconst clipped = liangBarsky(\n\t\t\t\toutputXY,\n\t\t\t\tinputXY,\n\t\t\t\tnode.area,\n\t\t\t\tclipA,\n\t\t\t\tclipB,\n\t\t\t);\n\n\t\t\tif (clipped === INSIDE) {\n\t\t\t\tconst centerX = (node.area[0] + ((node.area[2] - node.area[0]) / 2));\n\t\t\t\tconst centerY = (node.area[1] + ((node.area[3] - node.area[1]) / 2));\n\t\t\t\tconst dist = Math.sqrt(((centerX - outputXY[0]) ** 2) + ((centerY - outputXY[1]) ** 2));\n\t\t\t\tif (dist < closestDistance) {\n\t\t\t\t\tclosest = {\n\t\t\t\t\t\tstart: clipA,\n\t\t\t\t\t\tend: clipB,\n\t\t\t\t\t\tnode,\n\t\t\t\t\t};\n\t\t\t\t\tclosestDistance = dist;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { clipped: closest, closestDistance };\n\t}\n\n\ttestPath(path) {\n\t\tconst len1 = (path.length - 1);\n\t\tfor (let p = 0; p < len1; ++p) {\n\t\t\tconst { clipped } = this.findClippedNode(path[p], path[p + 1]);\n\t\t\tif (clipped) {\n\t\t\t\treturn clipped;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tmapFinalLink(outputXY, inputXY) {\n\t\tconst { clipped } = this.findClippedNode(outputXY, inputXY);\n\t\tif (!clipped) {\n\t\t\tconst dist = Math.sqrt(((outputXY[0] - inputXY[0]) ** 2) + ((outputXY[1] - inputXY[1]) ** 2));\n\t\t\tif (dist < this.maxDirectLineDistance) {\n\t\t\t\t// direct, nothing blocking us\n\t\t\t\treturn { path: [outputXY, inputXY] };\n\t\t\t}\n\t\t}\n\n\t\tconst horzDistance = inputXY[0] - outputXY[0];\n\t\tconst vertDistance = inputXY[1] - outputXY[1];\n\t\tconst horzDistanceAbs = Math.abs(horzDistance);\n\t\tconst vertDistanceAbs = Math.abs(vertDistance);\n\n\t\tif (horzDistanceAbs > vertDistanceAbs) {\n\t\t\t// we should never go left anyway,\n\t\t\t// because input slot is always on left and output is always on right\n\t\t\tconst goingLeft = inputXY[0] < outputXY[0];\n\t\t\tconst pathStraight45 = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[inputXY[0] - (goingLeft ? -vertDistanceAbs : vertDistanceAbs), outputXY[1]],\n\t\t\t\t[inputXY[0], inputXY[1]],\n\t\t\t];\n\t\t\t// __/\n\t\t\t//\n\t\t\t// __\n\t\t\t//   \\\n\t\t\tif (!this.testPath(pathStraight45)) {\n\t\t\t\treturn { path: pathStraight45 };\n\t\t\t}\n\n\t\t\tconst path45Straight = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[outputXY[0] + (goingLeft ? -vertDistanceAbs : vertDistanceAbs), inputXY[1]],\n\t\t\t\t[inputXY[0], inputXY[1]],\n\t\t\t];\n\t\t\t// \\__\n\t\t\t//\n\t\t\t//  __\n\t\t\t// /\n\t\t\tif (!this.testPath(path45Straight)) {\n\t\t\t\treturn { path: path45Straight };\n\t\t\t}\n\t\t} else {\n\t\t\t// move vert\n\t\t\tconst goingUp = inputXY[1] < outputXY[1];\n\t\t\tconst pathStraight45 = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[outputXY[0], inputXY[1] + (goingUp ? horzDistanceAbs : -horzDistanceAbs)],\n\t\t\t\t[inputXY[0], inputXY[1]],\n\t\t\t];\n\t\t\t// |\n\t\t\t// |\n\t\t\t//  \\\n\t\t\tif (!this.testPath(pathStraight45)) {\n\t\t\t\treturn { path: pathStraight45 };\n\t\t\t}\n\n\t\t\tconst path45Straight = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[inputXY[0], outputXY[1] - (goingUp ? horzDistanceAbs : -horzDistanceAbs)],\n\t\t\t\t[inputXY[0], inputXY[1]],\n\t\t\t];\n\t\t\t// \\\n\t\t\t//  |\n\t\t\t//  |\n\t\t\tif (!this.testPath(path45Straight)) {\n\t\t\t\treturn { path: path45Straight };\n\t\t\t}\n\t\t}\n\n\t\tconst path90Straight = [\n\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t[outputXY[0], inputXY[1]],\n\t\t\t[inputXY[0], inputXY[1]],\n\t\t];\n\t\t// |_\n\t\tconst clippedVert = this.testPath(path90Straight);\n\t\tif (!clippedVert) {\n\t\t\treturn { path: path90Straight };\n\t\t}\n\n\t\tconst pathStraight90 = [\n\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t[inputXY[0], outputXY[1]],\n\t\t\t[inputXY[0], inputXY[1]],\n\t\t];\n\t\t// _\n\t\t//  |\n\t\t//\n\t\t// _|\n\t\tconst clippedHorz = this.testPath(pathStraight90);\n\t\tif (!clippedHorz) {\n\t\t\t// add to lines area in destination node?\n\t\t\t// targetNodeInfo.linesArea[0] -= this.lineSpace;\n\t\t\treturn { path: pathStraight90 };\n\t\t}\n\t\treturn {\n\t\t\tclippedHorz,\n\t\t\tclippedVert,\n\t\t};\n\t}\n\n\tmapLink(outputXY, inputXY, targetNodeInfo, isBlocked /* , lastDirection */) {\n\t\tconst { clippedHorz, clippedVert, path } = this.mapFinalLink(outputXY, inputXY);\n\t\tif (path) {\n\t\t\treturn path;\n\t\t}\n\n\t\tconst horzDistance = inputXY[0] - outputXY[0];\n\t\tconst vertDistance = inputXY[1] - outputXY[1];\n\t\tconst horzDistanceAbs = Math.abs(horzDistance);\n\t\tconst vertDistanceAbs = Math.abs(vertDistance);\n\n\t\tlet blockedNodeId;\n\t\t// let blockedArea;\n\t\tlet pathAvoidNode;\n\t\tlet lastPathLocation;\n\t\tlet linesArea;\n\n\t\tlet thisDirection = null;\n\t\t// if (lastDirection !== 'horz' && horzDistanceAbs > vertDistanceAbs) {\n\t\tif (horzDistanceAbs > vertDistanceAbs) {\n\t\t\t// horz then vert to avoid blocking node\n\t\t\tblockedNodeId = clippedHorz.node.node.id;\n\t\t\t// blockedArea = clippedHorz.node.area;\n\t\t\tlinesArea = clippedHorz.node.linesArea;\n\t\t\tconst horzEdge = horzDistance <= 0\n\t\t\t\t? (linesArea[2])\n\t\t\t\t: (linesArea[0] - 1);\n\t\t\tpathAvoidNode = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[horzEdge, outputXY[1]],\n\t\t\t];\n\n\t\t\tif (horzDistance <= 0) {\n\t\t\t\tlinesArea[2] += this.lineSpace;\n\t\t\t} else {\n\t\t\t\tlinesArea[0] -= this.lineSpace;\n\t\t\t}\n\n\t\t\tconst vertDistanceViaBlockTop =\n\t\t\t\tMath.abs(inputXY[1] - linesArea[1]) +\n\t\t\t\tMath.abs(linesArea[1] - outputXY[1]);\n\t\t\tconst vertDistanceViaBlockBottom =\n\t\t\t\tMath.abs(inputXY[1] - linesArea[3]) +\n\t\t\t\tMath.abs(linesArea[3] - outputXY[1]);\n\n\t\t\tlastPathLocation = [\n\t\t\t\thorzEdge,\n\t\t\t\tvertDistanceViaBlockTop <= vertDistanceViaBlockBottom ?\n\t\t\t\t\t(linesArea[1])\n\t\t\t\t\t: (linesArea[3]),\n\t\t\t];\n\t\t\tconst unblockNotPossible1 = this.testPath([...pathAvoidNode, lastPathLocation]);\n\t\t\tif (unblockNotPossible1) {\n\t\t\t\tlastPathLocation = [\n\t\t\t\t\thorzEdge,\n\t\t\t\t\tvertDistanceViaBlockTop > vertDistanceViaBlockBottom ?\n\t\t\t\t\t\t(linesArea[1])\n\t\t\t\t\t\t: (linesArea[3]),\n\t\t\t\t];\n\t\t\t}\n\t\t\tif (lastPathLocation[1] < outputXY[1]) {\n\t\t\t\tlinesArea[1] -= this.lineSpace;\n\t\t\t\tlastPathLocation[1] -= 1;\n\t\t\t} else {\n\t\t\t\tlinesArea[3] += this.lineSpace;\n\t\t\t\tlastPathLocation[1] += 1;\n\t\t\t}\n\t\t\tthisDirection = 'vert';\n\t\t// } else if (lastDirection !== 'vert') {\n\t\t} else {\n\t\t\t// vert then horz to avoid blocking node\n\t\t\tblockedNodeId = clippedVert.node.node.id;\n\t\t\t// blockedArea = clippedVert.node.area;\n\t\t\tlinesArea = clippedVert.node.linesArea;\n\t\t\t// Special +/- 1 here because of the way it's calculated\n\t\t\tconst vertEdge =\n\t\t\t\tvertDistance <= 0\n\t\t\t\t\t? (linesArea[3] + 1)\n\t\t\t\t\t: (linesArea[1] - 1);\n\t\t\tpathAvoidNode = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[outputXY[0], vertEdge],\n\t\t\t];\n\t\t\tif (vertDistance <= 0) {\n\t\t\t\tlinesArea[3] += this.lineSpace;\n\t\t\t} else {\n\t\t\t\tlinesArea[1] -= this.lineSpace;\n\t\t\t}\n\n\t\t\tconst horzDistanceViaBlockLeft =\n\t\t\t\tMath.abs(inputXY[0] - linesArea[0]) +\n\t\t\t\tMath.abs(linesArea[0] - outputXY[0]);\n\t\t\tconst horzDistanceViaBlockRight =\n\t\t\t\tMath.abs(inputXY[0] - linesArea[2]) +\n\t\t\t\tMath.abs(linesArea[2] - outputXY[0]);\n\n\t\t\tlastPathLocation = [\n\t\t\t\thorzDistanceViaBlockLeft <= horzDistanceViaBlockRight ?\n\t\t\t\t\t(linesArea[0] - 1)\n\t\t\t\t\t: (linesArea[2]),\n\t\t\t\tvertEdge,\n\t\t\t];\n\t\t\tconst unblockNotPossible1 = this.testPath([...pathAvoidNode, lastPathLocation]);\n\t\t\tif (unblockNotPossible1) {\n\t\t\t\tlastPathLocation = [\n\t\t\t\t\thorzDistanceViaBlockLeft > horzDistanceViaBlockRight ?\n\t\t\t\t\t\t(linesArea[0])\n\t\t\t\t\t\t: (linesArea[2]),\n\t\t\t\t\tvertEdge,\n\t\t\t\t];\n\t\t\t}\n\t\t\tif (lastPathLocation[0] < outputXY[0]) {\n\t\t\t\tlinesArea[0] -= this.lineSpace;\n\t\t\t\t// lastPathLocation[0] -= 1; //this.lineSpace;\n\t\t\t} else {\n\t\t\t\tlinesArea[2] += this.lineSpace;\n\t\t\t\t// lastPathLocation[0] += 1; //this.lineSpace;\n\t\t\t}\n\t\t\tthisDirection = 'horz';\n\t\t\t//\t\t} else {\n\t\t\t//\t\t\tconsole.log('blocked will not go backwards', outputXY, inputXY);\n\t\t\t//\t\t\treturn [outputXY, inputXY];\n\t\t}\n\n\t\t// console.log('is blocked check',isBlocked, blockedNodeId);\n\t\tif (isBlocked[blockedNodeId] > 3) {\n\t\t\t// Blocked too many times, let's return the direct path\n\t\t\tconsole.log('Too many blocked', outputXY, inputXY); // eslint-disable-line no-console\n\t\t\treturn [outputXY, inputXY];\n\t\t}\n\t\tif (isBlocked[blockedNodeId])\n\t\t\t++isBlocked[blockedNodeId];\n\t\telse\n\t\t\tisBlocked[blockedNodeId] = 1;\n\t\t// console.log('pathavoid', pathAvoidNode);\n\t\tconst nextPath = this.mapLink(\n\t\t\tlastPathLocation,\n\t\t\tinputXY,\n\t\t\ttargetNodeInfo,\n\t\t\tisBlocked,\n\t\t\tthisDirection,\n\t\t);\n\t\treturn [...pathAvoidNode, lastPathLocation, ...nextPath.slice(1)];\n\t}\n\n\texpandSourceNodeLinesArea(sourceNodeInfo, path) {\n\t\tif (path.length < 3) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst linesArea = sourceNodeInfo.linesArea;\n\t\tif (path[1][0] === path[2][0]) {\n\t\t\t// first link is going vertical\n\t\t\t// while (path[1][0] > linesArea[2])\n\t\t\tlinesArea[2] += this.lineSpace;\n\t\t}\n\t\treturn true;\n\t}\n\n\t// expand left side of target node if we're going up there vertically.\n\texpandTargetNodeLinesArea(targetNodeInfo, path) {\n\t\tif (path.length < 2) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst linesArea = targetNodeInfo.linesArea;\n\t\tconst path2Len = path.length - 2;\n\t\tif (path[path2Len - 1][0] === path[path2Len][0]) {\n\t\t\t// first link is going vertical\n\t\t\t// while (path[path2Len][0] < linesArea[0])\n\t\t\tlinesArea[0] -= this.lineSpace;\n\t\t}\n\t\treturn true;\n\t}\n\n\tgetNodeOnPos(xy) {\n\t\tfor (let i = 0; i < this.nodesByRight.length; ++i) {\n\t\t\tconst nodeI = this.nodesByRight[i];\n\t\t\tconst { linesArea } = nodeI;\n\t\t\tif (xy[0] >= linesArea[0]\n\t\t\t\t&& xy[1] >= linesArea[1]\n\t\t\t\t&& xy[0] < linesArea[2]\n\t\t\t\t&& xy[1] < linesArea[3]\n\t\t\t) {\n\t\t\t\treturn nodeI;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tmapLinks(nodesByExecution) {\n\t\tif (!this.canvas.graph.links) {\n\t\t\tconsole.error('Missing graph.links', this.canvas.graph); // eslint-disable-line no-console\n\t\t\treturn;\n\t\t}\n\n\t\tconst startCalcTime = new Date().getTime();\n\t\tthis.links = [];\n\t\tthis.lastPathId = 1000000;\n\t\tthis.nodesByRight = [];\n\t\tthis.nodesById = {};\n\t\tthis.nodesByRight = nodesByExecution.map((node) => {\n\t\t\tconst barea = new Float32Array(4);\n\t\t\tnode.getBounding(barea);\n\t\t\tconst area = [\n\t\t\t\tbarea[0],\n\t\t\t\tbarea[1],\n\t\t\t\tbarea[0] + barea[2],\n\t\t\t\tbarea[1] + barea[3],\n\t\t\t];\n\t\t\tconst linesArea = Array.from(area);\n\t\t\tlinesArea[0] -= 5;\n\t\t\tlinesArea[1] -= 1;\n\t\t\tlinesArea[2] += 3;\n\t\t\tlinesArea[3] += 3;\n\t\t\tconst obj = {\n\t\t\t\tnode,\n\t\t\t\tarea,\n\t\t\t\tlinesArea,\n\t\t\t};\n\t\t\tthis.nodesById[node.id] = obj;\n\t\t\treturn obj;\n\t\t});\n\t\t//\n\t\t//\t\tthis.nodesByRight.sort(\n\t\t//\t\t\t(a, b) => (a.area[1]) - (b.area[1]),\n\t\t//\t\t);\n\n\t\tthis.nodesByRight.filter((nodeI) => {\n\t\t\tconst { node } = nodeI;\n\t\t\tif (!node.outputs) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tnode.outputs.filter((output, slot) => {\n\t\t\t\tif (!output.links) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tconst linkPos = new Float32Array(2);\n\t\t\t\tconst outputXYConnection = node.getConnectionPos(false, slot, linkPos);\n\t\t\t\tconst outputNodeInfo = this.nodesById[node.id];\n\t\t\t\tlet outputXY = Array.from(outputXYConnection);\n\t\t\t\toutput.links.filter((linkId) => {\n\t\t\t\t\toutputXY[0] = outputNodeInfo.linesArea[2];\n\t\t\t\t\tconst link = this.canvas.graph.links[linkId];\n\t\t\t\t\tif (!link) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tconst targetNode = this.canvas.graph.getNodeById(link.target_id);\n\t\t\t\t\tif (!targetNode) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst inputLinkPos = new Float32Array(2);\n\t\t\t\t\tconst inputXYConnection = targetNode.getConnectionPos(\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tlink.target_slot,\n\t\t\t\t\t\tinputLinkPos,\n\t\t\t\t\t);\n\t\t\t\t\tconst inputXY = Array.from(inputXYConnection);\n\t\t\t\t\tconst nodeInfo = this.nodesById[targetNode.id];\n\t\t\t\t\tinputXY[0] = nodeInfo.linesArea[0] - 1;\n\n\t\t\t\t\tconst inputBlockedByNode =\n\t\t\t\t\t\tthis.getNodeOnPos(inputXY);\n\t\t\t\t\tconst outputBlockedByNode =\n\t\t\t\t\t\tthis.getNodeOnPos(outputXY);\n\n\t\t\t\t\tlet path = null;\n\t\t\t\t\t// console.log('blocked', inputBlockedByNode, outputBlockedByNode,\n\t\t\t\t\t//\t'inputXY', inputXY, 'outputXY', outputXY);\n\t\t\t\t\tif (!inputBlockedByNode && !outputBlockedByNode) {\n\t\t\t\t\t\tconst pathFound = this.mapLink(outputXY, inputXY, nodeInfo, {}, null);\n\t\t\t\t\t\tif (pathFound && pathFound.length > 2) {\n\t\t\t\t\t\t\t// mapLink() may have expanded the linesArea,\n\t\t\t\t\t\t\t// lets put it back into the inputXY so the line is straight\n\t\t\t\t\t\t\tpath = [outputXYConnection, ...pathFound, inputXYConnection];\n\t\t\t\t\t\t\tthis.expandTargetNodeLinesArea(nodeInfo, path);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!path) {\n\t\t\t\t\t\tpath = [outputXYConnection, outputXY, inputXY, inputXYConnection];\n\t\t\t\t\t}\n\t\t\t\t\tthis.expandSourceNodeLinesArea(nodeI, path);\n\t\t\t\t\tthis.paths.push({\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tnode,\n\t\t\t\t\t\ttargetNode,\n\t\t\t\t\t\tslot,\n\t\t\t\t\t});\n\t\t\t\t\toutputXY = [\n\t\t\t\t\t\toutputXY[0] + this.lineSpace,\n\t\t\t\t\t\toutputXY[1],\n\t\t\t\t\t];\n\t\t\t\t\treturn false;\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t});\n\t\t\treturn false;\n\t\t});\n\t\tthis.lastCalculate = new Date().getTime();\n\t\tthis.lastCalcTime = this.lastCalculate - startCalcTime;\n\n\t\tif (this.debug)\n\t\t\tconsole.log('last calc time', this.lastCalcTime); // eslint-disable-line no-console\n\t\t// console.log('nodesbyright', this.nodesByRight);\n\t\t// Uncomment this to test timeout on draws\n\t\t// this.lastCalcTime = 250;\n\t}\n\n\tdrawLinks(ctx) {\n\t\tif (!this.canvas.default_connection_color_byType || !this.canvas.default_connection_color) {\n\t\t\tconsole.error('Missing canvas.default_connection_color_byType', this.canvas); // eslint-disable-line no-console\n\t\t\treturn;\n\t\t}\n\t\tif (this.debug)\n\t\t\tconsole.log('paths', this.paths); // eslint-disable-line no-console\n\n\t\tctx.save();\n\t\tconst currentNodeIds = this.canvas.selected_nodes || {};\n\t\tconst corners = [];\n\t\tthis.paths.filter((pathI) => {\n\t\t\tconst path = pathI.path;\n\t\t\tconst connection = pathI.node.outputs[pathI.slot];\n\t\t\tif (path.length <= 1) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tctx.beginPath();\n\t\t\tconst slotColor =\n\t\t\t\tthis.canvas.default_connection_color_byType[connection.type]\n\t\t\t\t|| this.canvas.default_connection_color.input_on;\n\n\t\t\tif (currentNodeIds[pathI.node.id] || currentNodeIds[pathI.targetNode.id]) {\n\t\t\t\tctx.strokeStyle = 'white';\n\t\t\t} else {\n\t\t\t\tctx.strokeStyle = slotColor;\n\t\t\t}\n\t\t\tctx.lineWidth = 3;\n\t\t\tconst cornerRadius = this.lineSpace;\n\n\t\t\tlet isPrevDotRound = false;\n\t\t\tfor (let p = 0; p < path.length; ++p) {\n\t\t\t\tconst pos = path[p];\n\n\t\t\t\tif (p === 0) {\n\t\t\t\t\tctx.moveTo(pos[0], pos[1]);\n\t\t\t\t}\n\t\t\t\tconst prevPos = pos;\n\t\t\t\tconst cornerPos = path[p + 1];\n\t\t\t\tconst nextPos = path[p + 2];\n\n\t\t\t\tlet drawn = false;\n\t\t\t\tif (nextPos) {\n\t\t\t\t\tconst xDiffBefore = cornerPos[0] - prevPos[0];\n\t\t\t\t\tconst yDiffBefore = cornerPos[1] - prevPos[1];\n\t\t\t\t\tconst xDiffAfter = nextPos[0] - cornerPos[0];\n\t\t\t\t\tconst yDiffAfter = nextPos[1] - cornerPos[1];\n\t\t\t\t\tconst isBeforeStraight = xDiffBefore === 0 || yDiffBefore === 0;\n\t\t\t\t\tconst isAfterStraight = xDiffAfter === 0 || yDiffAfter === 0;\n\t\t\t\t\t// up/down -> left/right\n\t\t\t\t\tif (\n\t\t\t\t\t\t(isBeforeStraight || isAfterStraight)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst beforePos = [\n\t\t\t\t\t\t\tcornerPos[0],\n\t\t\t\t\t\t\tcornerPos[1],\n\t\t\t\t\t\t];\n\t\t\t\t\t\tconst afterPos = [\n\t\t\t\t\t\t\tcornerPos[0],\n\t\t\t\t\t\t\tcornerPos[1],\n\t\t\t\t\t\t];\n\n\t\t\t\t\t\tif (isBeforeStraight) {\n\t\t\t\t\t\t\tconst xSignBefore = Math.sign(xDiffBefore);\n\t\t\t\t\t\t\tconst ySignBefore = Math.sign(yDiffBefore);\n\t\t\t\t\t\t\tbeforePos[0] = cornerPos[0] - cornerRadius * xSignBefore;\n\t\t\t\t\t\t\tbeforePos[1] = cornerPos[1] - cornerRadius * ySignBefore;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (isAfterStraight) {\n\t\t\t\t\t\t\tconst xSignAfter = Math.sign(xDiffAfter);\n\t\t\t\t\t\t\tconst ySignAfter = Math.sign(yDiffAfter);\n\t\t\t\t\t\t\tafterPos[0] = cornerPos[0] + cornerRadius * xSignAfter;\n\t\t\t\t\t\t\tafterPos[1] = cornerPos[1] + cornerRadius * ySignAfter;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (isPrevDotRound\n\t\t\t\t\t\t\t&& Math.abs(isPrevDotRound[0] - beforePos[0]) <= cornerRadius\n\t\t\t\t\t\t\t&& Math.abs(isPrevDotRound[1] - beforePos[1]) <= cornerRadius\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t// if two rounded corners are too close, draw a straight line so it doesn't look funny\n\t\t\t\t\t\t\tctx.lineTo(cornerPos[0], cornerPos[1]);\n\t\t\t\t\t\t\t// ctx.lineTo(beforePos[0], beforePos[1]);\n\t\t\t\t\t\t\t// ctx.lineTo(afterPos[0], afterPos[1]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tctx.lineTo(beforePos[0], beforePos[1]);\n\t\t\t\t\t\t\tcorners.push(cornerPos);\n\t\t\t\t\t\t\tctx.quadraticCurveTo(cornerPos[0], cornerPos[1], afterPos[0], afterPos[1]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tisPrevDotRound = beforePos;\n\t\t\t\t\t\tdrawn = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (p > 0 && !drawn) {\n\t\t\t\t\tif (!isPrevDotRound) {\n\t\t\t\t\t\tctx.lineTo(pos[0], pos[1]);\n\t\t\t\t\t}\n\t\t\t\t\tisPrevDotRound = false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tctx.stroke();\n\t\t\tctx.closePath();\n\t\t\treturn false;\n\t\t});\n\n\t\tif (this.debug) {\n\t\t\tcorners.filter((corn) => {\n\t\t\t\tctx.strokeStyle = '#ff00ff';\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc(corn[0], corn[1], 1, 0, 2 * Math.PI);\n\t\t\t\tctx.stroke();\n\t\t\t\treturn false;\n\t\t\t});\n\n\t\t\tthis.nodesByRight.filter((nodeI) => {\n\t\t\t\tctx.lineWidth = 1;\n\t\t\t\tctx.strokeStyle = '#000080';\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.rect(\n\t\t\t\t\tnodeI.area[0],\n\t\t\t\t\tnodeI.area[1],\n\t\t\t\t\tnodeI.area[2] - nodeI.area[0],\n\t\t\t\t\tnodeI.area[3] - nodeI.area[1],\n\t\t\t\t);\n\t\t\t\tctx.stroke();\n\t\t\t\tctx.closePath();\n\n\t\t\t\tctx.strokeStyle = '#0000a0';\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.rect(\n\t\t\t\t\tnodeI.linesArea[0],\n\t\t\t\t\tnodeI.linesArea[1],\n\t\t\t\t\tnodeI.linesArea[2] - nodeI.linesArea[0],\n\t\t\t\t\tnodeI.linesArea[3] - nodeI.linesArea[1],\n\t\t\t\t);\n\t\t\t\tctx.stroke();\n\t\t\t\tctx.closePath();\n\t\t\t\treturn false;\n\t\t\t});\n\t\t}\n\n\t\tctx.restore();\n\t}\n}\n\nclass EyeButton {\n\tconstructor() {\n\t\tthis.hidden = null;\n\t}\n\n\tstatic getEyeButton() {\n\t\tconst eyeButtons = document.querySelectorAll('.pi-eye,.pi-eye-slash');\n\t\tif (eyeButtons.length > 1) {\n\t\t\tconsole.log('found too many eye buttons', eyeButtons); // eslint-disable-line no-console\n\t\t}\n\t\treturn eyeButtons[0];\n\t}\n\n\tcheck() {\n\t\tconst eyeButton = EyeButton.getEyeButton();\n\t\tif (!eyeButton) {\n\t\t\treturn;\n\t\t}\n\t\tconst hidden = eyeButton.classList.contains('pi-eye-slash');\n\t\tif (this.hidden !== hidden) {\n\t\t\tthis.hidden = hidden;\n\t\t\tif (this.onChange) {\n\t\t\t\tthis.onChange(hidden);\n\t\t\t}\n\t\t}\n\t}\n\n\tlistenEyeButton(onChange) {\n\t\tthis.onChange = onChange;\n\t\tconst eyeButton = EyeButton.getEyeButton();\n\t\tif (!eyeButton) {\n\t\t\tsetTimeout(() => this.listenEyeButton(onChange), 1000);\n\t\t\treturn;\n\t\t}\n\t\tconst eyeDom = eyeButton.parentNode;\n\t\teyeDom.addEventListener('click', () => this.check());\n\t\teyeDom.addEventListener('keyup', () => this.check());\n\t\teyeDom.addEventListener('mouseup', () => this.check());\n\t}\n}\n\nexport class CircuitBoardLines {\n\tconstructor() {\n\t\tthis.canvas = null;\n\t\tthis.mapLinks = null;\n\t\tthis.enabled = true;\n\t\tthis.eyeHidden = false;\n\t\tthis.maxDirectLineDistance = Number.MAX_SAFE_INTEGER;\n\t}\n\n\tsetEnabled(e) { this.enabled = e; }\n\n\tisShow() { return this.enabled && !this.eyeHidden; }\n\n\trecalcMapLinksTimeout() {\n\t\t// calculate paths when user is idle...\n\t\tif (!this.skipNextRecalcTimeout) {\n\t\t\tif (this.recalcTimeout) {\n\t\t\t\tclearTimeout(this.recalcTimeout);\n\t\t\t\tthis.recalcTimeout = null;\n\t\t\t}\n\n\t\t\tthis.recalcTimeout = setTimeout(() => {\n\t\t\t\tthis.recalcTimeout = null;\n\t\t\t\tthis.recalcMapLinks();\n\t\t\t\tthis.redraw();\n\t\t\t}, this.mapLinks.lastCalcTime * 2);\n\t\t}\n\t\tthis.skipNextRecalcTimeout = false;\n\t}\n\n\tredraw() {\n\t\tif (this.lastDrawTimeout) {\n\t\t\tclearTimeout(this.lastDrawTimeout);\n\t\t\tthis.lastDrawTimeout = null;\n\t\t}\n\n\t\tthis.lastDrawTimeout = setTimeout(() => {\n\t\t\tthis.lastDrawTimeout = null;\n\t\t\twindow.requestAnimationFrame(() => {\n\t\t\t\tconsole.log('redraw timeout'); // eslint-disable-line no-console\n\t\t\t\tthis.canvas.setDirty(true, true);\n\t\t\t\tthis.skipNextRecalcTimeout = true;\n\t\t\t\tthis.canvas.draw(true, true);\n\t\t\t});\n\t\t}, 0);\n\t}\n\n\trecalcMapLinksCheck() {\n\t\tif (this.mapLinks) {\n\t\t\tif (this.mapLinks.lastCalcTime > 100) {\n\t\t\t\tthis.recalcMapLinksTimeout();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tthis.recalcMapLinks();\n\t\treturn true;\n\t}\n\n\trecalcMapLinks() {\n\t\tthis.mapLinks = new MapLinks(this.canvas);\n\t\tthis.mapLinks.maxDirectLineDistance = this.maxDirectLineDistance;\n\t\tthis.mapLinks.debug = this.debug;\n\t\tconst nodesByExecution = this.canvas.graph.computeExecutionOrder() || [];\n\t\tthis.mapLinks.mapLinks(nodesByExecution);\n\t}\n\n\tdrawConnections(\n\t\tctx,\n\t) {\n\t\tif (!this.canvas || !this.canvas.graph) {\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recalcMapLinksCheck();\n\n\t\t\tthis.mapLinks.drawLinks(ctx);\n\n\t\t\tif (this.canvas.subgraph) {\n\t\t\t\tthis.drawSubgraphConnections(ctx, this.canvas.graph, this.canvas.subgraph);\n\t\t\t}\n\t\t} catch(e) {\n\t\t\tconsole.error('drawConnections crash', e);\n\t\t} finally {\n\t\t\tthis.lastDrawConnections = new Date().getTime();\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tdrawSubgraphConnections(\n\t\tctx,\n\t\tgraph,\n\t\tsubgraph,\n\t) {\n\t\tfor (const output of subgraph.inputNode.slots) { // eslint-disable-line no-restricted-syntax\n\t\t\tif (!output.linkIds.length) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// find link info\n\t\t\tfor (const linkId of output.linkIds) { // eslint-disable-line no-restricted-syntax\n\t\t\t\tconst resolved = LiteGraph.LLink.resolve(linkId, graph);\n\t\t\t\tif (!resolved) continue;\n\n\t\t\t\tconst { link, inputNode, input } = resolved;\n\t\t\t\tif (!inputNode || !input)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tconst endPos = inputNode.getInputPos(link.target_slot);\n\n\t\t\t\tconst startDir = input.dir || LiteGraph.RIGHT;\n\t\t\t\tconst endDir = input.dir || LiteGraph.LEFT;\n\n\t\t\t\tthis.canvas.renderLink(\n\t\t\t\t\tctx,\n\t\t\t\t\toutput.pos,\n\t\t\t\t\tendPos,\n\t\t\t\t\tlink,\n\t\t\t\t\tfalse,\n\t\t\t\t\t0,\n\t\t\t\t\tnull,\n\t\t\t\t\tstartDir,\n\t\t\t\t\tendDir,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tfor (const input of subgraph.outputNode.slots) { // eslint-disable-line no-restricted-syntax\n\t\t\tif (!input.linkIds.length) continue;\n\n\t\t\t// find link info\n\t\t\tconst resolved = LiteGraph.LLink.resolve(input.linkIds[0], graph);\n\t\t\tif (!resolved) continue;\n\n\t\t\tconst { link, outputNode, output } = resolved;\n\t\t\tif (!outputNode || !output) continue;\n\n\t\t\tconst startPos = outputNode.getOutputPos(link.origin_slot);\n\n\t\t\tconst startDir = output.dir || LiteGraph.RIGHT;\n\t\t\tconst endDir = input.dir || LiteGraph.LEFT;\n\n\t\t\tthis.canvas.renderLink(\n\t\t\t\tctx,\n\t\t\t\tstartPos,\n\t\t\t\tinput.pos,\n\t\t\t\tlink,\n\t\t\t\tfalse,\n\t\t\t\t0,\n\t\t\t\tnull,\n\t\t\t\tstartDir,\n\t\t\t\tendDir,\n\t\t\t);\n\t\t}\n\t}\n\n\tinit() {\n\t\tconst oldDrawConnections = LGraphCanvas.prototype.drawConnections;\n\t\tconst t = this;\n\t\tLGraphCanvas.prototype.drawConnections = function drawConnections(\n\t\t\tctx,\n\t\t) {\n\t\t\tif (t.canvas && t.isShow()) {\n\t\t\t\treturn t.drawConnections(\n\t\t\t\t\tctx,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn oldDrawConnections.apply(this, arguments);\n\t\t};\n\t\tthis.eyeButton = new EyeButton();\n\t\tthis.eyeButton.listenEyeButton((hidden) => {\n\t\t\tthis.eyeHidden = hidden;\n\t\t});\n\t}\n\n\tinitOverrides(canvas) {\n\t\tthis.canvas = canvas;\n\t}\n}\n"
  },
  {
    "path": "docs/QuickConnection.js",
    "content": "/* eslint no-tabs: 0 */\n/* eslint import/prefer-default-export:0 */\n/* eslint no-underscore-dangle:0 */\n/* eslint no-plusplus:0 */\n/* eslint prefer-rest-params:0 */\n/* eslint operator-linebreak:0 */\n/* eslint no-unneeded-ternary:0 */\n/* global LGraphCanvas */\n/* global LiteGraph */\n\nexport class QuickConnection {\n\tconstructor() {\n\t\tthis.insideConnection = null;\n\t\tthis.enabled = false;\n\t\t// use inputs that already have a link to them.\n\t\tthis.useInputsWithLinks = false;\n\t\tthis.release_link_on_empty_shows_menu = true;\n\t\tthis.connectDotOnly = true;\n\t\tthis.doNotAcceptType = /^\\*$/;\n\t\tthis.boxAlpha = 0.7;\n\t\tthis.boxBackground = '#000';\n\t}\n\n\tinit() {\n\t\tconst origProcessMouseDown = LGraphCanvas.prototype.processMouseDown;\n\t\tconst t = this;\n\t\tthis.acceptingNodes = null;\n\t\tLGraphCanvas.prototype.processMouseDown = function mouseDown() {\n\t\t\tt.pointerDown();\n\t\t\tconst ret = origProcessMouseDown.apply(this, arguments);\n\t\t\treturn ret;\n\t\t};\n\n\t\tconst origProcessMouseUp = LGraphCanvas.prototype.processMouseUp;\n\t\tLGraphCanvas.prototype.processMouseUp = function mouseUp() {\n\t\t\tif (!t.enabled) {\n\t\t\t\treturn origProcessMouseUp.apply(this, arguments);\n\t\t\t}\n\n\t\t\t// Let's not popup the release on empty spot menu if we've released the mouse on a dot\n\t\t\tconst origReleaseLink = LiteGraph.release_link_on_empty_shows_menu;\n\t\t\tconst origShowConnectionMenu = t.canvas.showConnectionMenu;\n\n\t\t\tlet ret = null;\n\t\t\ttry {\n\t\t\t\tif (t.pointerUp()) {\n\t\t\t\t\tif (!t.isComfyUI) {\n\t\t\t\t\t\tLiteGraph.release_link_on_empty_shows_menu = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.canvas.showConnectionMenu = () => {};\n\t\t\t\t\t}\n\t\t\t\t\tt.release_link_on_empty_shows_menu = false;\n\t\t\t\t}\n\t\t\t\tret = origProcessMouseUp.apply(this, arguments);\n\t\t\t} finally {\n\t\t\t\tif (!t.release_link_on_empty_shows_menu) {\n\t\t\t\t\tif (!t.isComfyUI) {\n\t\t\t\t\t\tLiteGraph.release_link_on_empty_shows_menu = origReleaseLink;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.canvas.showConnectionMenu = origShowConnectionMenu;\n\t\t\t\t\t\tt.canvas.linkConnector.reset();\n\t\t\t\t\t}\n\t\t\t\t\tt.release_link_on_empty_shows_menu = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn ret;\n\t\t};\n\n\t\t// ComfyUI has it's own version of litegraph.js\n\t\t// https://github.com/Comfy-Org/litegraph.js\n\t}\n\n\tinitListeners(canvas) {\n\t\tthis.enabled = true;\n\t\tthis.graph = canvas.graph;\n\t\tthis.canvas = canvas;\n\t\tif (!this.canvas.canvas) {\n\t\t\tconsole.error('no canvas', this.canvas); // eslint-disable-line no-console\n\t\t} else {\n\t\t\tthis.canvas.canvas.addEventListener('litegraph:canvas', (e) => {\n\t\t\t\tconst { detail } = e;\n\t\t\t\tif (!this.release_link_on_empty_shows_menu\n\t\t\t\t\t&& detail && detail.subType === 'empty-release'\n\t\t\t\t) {\n\t\t\t\t\te.stopPropagation();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tthis.isComfyUI = this.canvas.connecting_links !== undefined ? true : false;\n\n\t\tthis.addOnCanvas('onDrawOverlay', (ctx) => this.onDrawOverlay(ctx));\n\t}\n\n\tgetCurrentConnection() {\n\t\tif (this.isComfyUI) {\n\t\t\tconst connectingLink =\n\t\t\t\t(this.canvas.connecting_links\n\t\t\t\t\t&& this.canvas.connecting_links.length > 0\n\t\t\t\t) ?\n\t\t\t\t\tthis.canvas.connecting_links[0] : null;\n\t\t\tif (connectingLink) {\n\t\t\t\treturn {\n\t\t\t\t\tnode: connectingLink.node,\n\t\t\t\t\tslot: connectingLink.slot,\n\t\t\t\t\tinput: connectingLink.input,\n\t\t\t\t\toutput: connectingLink.output,\n\t\t\t\t};\n\t\t\t}\n\t\t} else if (this.canvas.connecting_node) {\n\t\t\treturn {\n\t\t\t\tnode: this.canvas.connecting_node,\n\t\t\t\tinput: this.canvas.connecting_input,\n\t\t\t\tslot: this.canvas.connecting_slot,\n\t\t\t\toutput: this.canvas.connecting_output,\n\t\t\t};\n\t\t}\n\t\treturn null;\n\t}\n\n\tpointerDown() {\n\t\tthis.acceptingNodes = null;\n\t\treturn false;\n\t}\n\n\tpointerUp() {\n\t\tthis.acceptingNodes = null;\n\t\tconst connectionInfo = this.getCurrentConnection();\n\n\t\tif (this.insideConnection && connectionInfo) {\n\t\t\tif (connectionInfo.input) {\n\t\t\t\tthis.insideConnection.node.connect(\n\t\t\t\t\tthis.insideConnection.connection_slot_index,\n\t\t\t\t\tconnectionInfo.node,\n\t\t\t\t\tconnectionInfo.slot,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tconnectionInfo.node.connect(\n\t\t\t\t\tconnectionInfo.slot,\n\t\t\t\t\tthis.insideConnection.node,\n\t\t\t\t\tthis.insideConnection.connection_slot_index,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tfindAcceptingNodes(fromConnection, fromNode, findInput) {\n\t\tconst accepting = [];\n\t\tif (this.doNotAcceptType.exec(fromConnection.type)) {\n\t\t\t// Too many connections are available if we area a * connection\n\t\t\treturn accepting;\n\t\t}\n\t\tconst addToAccepting = (arr, node) => {\n\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\tif (node.mode == 4) {\n\t\t\t\t// bypassed\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (node.id === fromNode.id) {\n\t\t\t\t// Don't connect to myself\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (let c = 0; c < arr.length; ++c) {\n\t\t\t\tconst input = arr[c];\n\t\t\t\tif (!input.link || this.useInputsWithLinks) {\n\t\t\t\t\tconst accept = LiteGraph.isValidConnection(\n\t\t\t\t\t\tinput.type,\n\t\t\t\t\t\tfromConnection.type,\n\t\t\t\t\t);\n\t\t\t\t\tif (accept && !this.doNotAcceptType.exec(input.type)) {\n\t\t\t\t\t\taccepting.push({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tconnection: input,\n\t\t\t\t\t\t\tconnection_slot_index: c,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst nodes = this.graph._nodes;\n\t\tfor (let i = 0; i < nodes.length; ++i) {\n\t\t\tconst node = nodes[i];\n\t\t\tif (node.inputs && findInput) {\n\t\t\t\taddToAccepting(node.inputs, node);\n\t\t\t}\n\t\t\tif (node.outputs && !findInput) {\n\t\t\t\taddToAccepting(node.outputs, node);\n\t\t\t}\n\t\t}\n\n\t\taccepting.sort((a, b) => a.node.pos[1] - b.node.pos[1]);\n\t\treturn accepting;\n\t}\n\n\taddOnCanvas(name, func) {\n\t\tconst obj = this.canvas;\n\t\tconst oldFunc = obj[name];\n\t\tobj[name] = function callFunc() {\n\t\t\tif (oldFunc) {\n\t\t\t\toldFunc.apply(obj, arguments);\n\t\t\t}\n\t\t\treturn func.apply(obj, arguments);\n\t\t};\n\t}\n\n\tonDrawOverlay(ctx) {\n\t\tif (!this.enabled) {\n\t\t\treturn;\n\t\t}\n\t\tif (!this.canvas || !this.canvas.graph_mouse) {\n\t\t\tconsole.error('no canvas or mouse yet', this.canvas); // eslint-disable-line no-console\n\t\t\treturn;\n\t\t}\n\n\t\tthis.insideConnection = null;\n\n\t\tconst connectionInfo = this.getCurrentConnection();\n\n\t\tif (connectionInfo) {\n\t\t\tconst {\n\t\t\t\tnode, input, output, slot,\n\t\t\t} = connectionInfo;\n\t\t\tif (!input && !output) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tctx.save();\n\t\t\tthis.canvas.ds.toCanvasContext(ctx);\n\n\t\t\tconst slotPos = new Float32Array(2);\n\n\t\t\tconst isInput = input ? true : false;\n\t\t\tconst connecting = isInput ? input : output;\n\t\t\tconst connectionSlot = slot;\n\n\t\t\tconst pos = node.getConnectionPos(isInput, connectionSlot, slotPos);\n\n\t\t\tif (!this.acceptingNodes) {\n\t\t\t\tthis.acceptingNodes = this.findAcceptingNodes(\n\t\t\t\t\tconnecting,\n\t\t\t\t\t// this.canvas.connecting_node,\n\t\t\t\t\tnode,\n\t\t\t\t\t!isInput,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst mouseX = this.canvas.graph_mouse[0];\n\t\t\tconst mouseY = this.canvas.graph_mouse[1];\n\n\t\t\t// const hasNodeTooltip = document.querySelector('.node-tooltip');\n\n\t\t\tconst buttonShift = [\n\t\t\t\tisInput ? -32 : +32,\n\t\t\t\t// force for now so the dots don't move around when the tooltip pops up.\n\t\t\t\t// No need to avoid tool tip if we're using the input,\n\t\t\t\t//\ttool tip is visible to the right of dot\n\t\t\t\tisInput ? 0 : LiteGraph.NODE_SLOT_HEIGHT,\n\t\t\t\t/*\n\t\t\t\t(true || this.acceptingNodes.length === 1 || hasNodeTooltip)\n\t\t\t\t\t? 0\n\t\t\t\t\t: (\n\t\t\t\t\t\t((-this.acceptingNodes.length * LiteGraph.NODE_SLOT_HEIGHT) / 2)\n\t\t\t\t\t\t+ (LiteGraph.NODE_SLOT_HEIGHT / 2)\n\t\t\t\t\t),\n\t\t\t\t\t*/\n\t\t\t];\n\t\t\tconst linkPos = [\n\t\t\t\tpos[0] + buttonShift[0],\n\t\t\t\tpos[1] + buttonShift[1],\n\t\t\t];\n\n\t\t\tlet scale = 1 / this.canvas.ds.scale;\n\t\t\tif (scale < 1.0) {\n\t\t\t\tscale = 1.0;\n\t\t\t}\n\n\t\t\tconst linkCloseArea = [\n\t\t\t\tlinkPos[0] - (LiteGraph.NODE_SLOT_HEIGHT * 6 * scale),\n\t\t\t\tlinkPos[1] - LiteGraph.NODE_SLOT_HEIGHT,\n\t\t\t\tLiteGraph.NODE_SLOT_HEIGHT * 8 * scale,\n\t\t\t\tLiteGraph.NODE_SLOT_HEIGHT * (this.acceptingNodes.length + 1) * scale,\n\t\t\t];\n\t\t\tif (!isInput) {\n\t\t\t\tlinkCloseArea[0] = linkPos[0] - ((LiteGraph.NODE_SLOT_HEIGHT * 2) * scale);\n\t\t\t}\n\n\t\t\tconst isInsideClosePosition = LiteGraph.isInsideRectangle(\n\t\t\t\tmouseX,\n\t\t\t\tmouseY,\n\t\t\t\tlinkCloseArea[0],\n\t\t\t\tlinkCloseArea[1],\n\t\t\t\tlinkCloseArea[2],\n\t\t\t\tlinkCloseArea[3],\n\t\t\t);\n\t\t\tlet boxRect = null;\n\t\t\tconst textsToDraw = [];\n\n\t\t\t// const oldFillStyle = ctx.fillStyle;\n\t\t\tif (isInsideClosePosition) {\n\t\t\t\tconst oldFont = ctx.font;\n\t\t\t\tlet font = oldFont;\n\t\t\t\tconst fontM = /([0-9]+)px/.exec(font);\n\t\t\t\tif (!fontM) {\n\t\t\t\t\tfontM[1] = 'px';\n\t\t\t\t\tfont += ' 12px';\n\t\t\t\t}\n\t\t\t\tif (fontM) {\n\t\t\t\t\tconst fontSize = parseInt(fontM[1], 10) * scale;\n\t\t\t\t\tctx.font = font.replace(/[0-9]+px/, `${fontSize}px`);\n\t\t\t\t}\n\t\t\t\tthis.acceptingNodes.filter((acceptingNode) => {\n\t\t\t\t\tconst textxy = [\n\t\t\t\t\t\tlinkPos[0] + (isInput ? -LiteGraph.NODE_SLOT_HEIGHT : LiteGraph.NODE_SLOT_HEIGHT),\n\t\t\t\t\t\tlinkPos[1],\n\t\t\t\t\t];\n\n\t\t\t\t\tconst acceptingText = `${acceptingNode.connection.name} @${acceptingNode.node.title}`;\n\t\t\t\t\tconst textBox = ctx.measureText(acceptingText);\n\t\t\t\t\tconst box = [\n\t\t\t\t\t\ttextxy[0],\n\t\t\t\t\t\ttextxy[1] - textBox.fontBoundingBoxAscent,\n\t\t\t\t\t\ttextBox.width,\n\t\t\t\t\t\tLiteGraph.NODE_SLOT_HEIGHT,\n\t\t\t\t\t\t// (textBox.fontBoundingBoxAscent + textBox.fontBoundingBoxDescent),\n\t\t\t\t\t];\n\n\t\t\t\t\tlet textAlign;\n\t\t\t\t\tif (!isInput) {\n\t\t\t\t\t\ttextAlign = 'left';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbox[0] -= textBox.width;\n\t\t\t\t\t\ttextAlign = 'right';\n\t\t\t\t\t}\n\n\t\t\t\t\tconst rRect = [\n\t\t\t\t\t\tbox[0] - 8 * scale,\n\t\t\t\t\t\tbox[1] - 4 * scale,\n\t\t\t\t\t\tbox[2] + 16 * scale,\n\t\t\t\t\t\tbox[3], // + 5 * scale,\n\t\t\t\t\t];\n\t\t\t\t\tif (!boxRect) {\n\t\t\t\t\t\tboxRect = rRect.slice(0);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (boxRect[0] > rRect[0]) {\n\t\t\t\t\t\t\t// eslint-disable-next-line prefer-destructuring\n\t\t\t\t\t\t\tboxRect[0] = rRect[0];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (boxRect[2] < rRect[2]) {\n\t\t\t\t\t\t\t// eslint-disable-next-line prefer-destructuring\n\t\t\t\t\t\t\tboxRect[2] = rRect[2];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tboxRect[3] += rRect[3];\n\t\t\t\t\t}\n\n\t\t\t\t\ttextsToDraw.push({\n\t\t\t\t\t\tx: textxy[0],\n\t\t\t\t\t\ty: textxy[1],\n\t\t\t\t\t\tacceptingText,\n\t\t\t\t\t\ttextAlign,\n\t\t\t\t\t});\n\n\t\t\t\t\tlet isInsideRect;\n\t\t\t\t\tif (this.connectDotOnly) {\n\t\t\t\t\t\tisInsideRect = LiteGraph.isInsideRectangle(\n\t\t\t\t\t\t\tmouseX,\n\t\t\t\t\t\t\tmouseY,\n\t\t\t\t\t\t\tlinkPos[0] - ((LiteGraph.NODE_SLOT_HEIGHT / 2) * scale),\n\t\t\t\t\t\t\tlinkPos[1] - ((LiteGraph.NODE_SLOT_HEIGHT / 2) * scale),\n\t\t\t\t\t\t\tLiteGraph.NODE_SLOT_HEIGHT * scale,\n\t\t\t\t\t\t\tLiteGraph.NODE_SLOT_HEIGHT * scale,\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tisInsideRect = LiteGraph.isInsideRectangle(\n\t\t\t\t\t\t\tmouseX,\n\t\t\t\t\t\t\tmouseY,\n\t\t\t\t\t\t\tisInput ? box[0] : (linkPos[0] - (LiteGraph.NODE_SLOT_HEIGHT / 2)),\n\t\t\t\t\t\t\tlinkPos[1] - 10,\n\t\t\t\t\t\t\tisInput ?\n\t\t\t\t\t\t\t\t((linkPos[0] - box[0]) + LiteGraph.NODE_SLOT_HEIGHT / 2)\n\t\t\t\t\t\t\t\t: (rRect[2] + LiteGraph.NODE_SLOT_HEIGHT / 2),\n\t\t\t\t\t\t\trRect[3],\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (isInsideRect && !this.insideConnection) {\n\t\t\t\t\t\tthis.insideConnection = acceptingNode;\n\t\t\t\t\t\tctx.fillStyle = LiteGraph.EVENT_LINK_COLOR; // \"#ffcc00\";\n\t\t\t\t\t\t// highlight destination if mouseover\n\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\tctx.arc(\n\t\t\t\t\t\t\tlinkPos[0],\n\t\t\t\t\t\t\tlinkPos[1],\n\t\t\t\t\t\t\t6 * scale,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tMath.PI * 2,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\tctx.closePath();\n\n\t\t\t\t\t\tctx.beginPath();\n\n\t\t\t\t\t\tctx.strokeStyle = '#6a6';\n\t\t\t\t\t\tctx.setLineDash([5, 10]);\n\t\t\t\t\t\tctx.lineWidth = 3;\n\n\t\t\t\t\t\tconst aNode = acceptingNode.node;\n\t\t\t\t\t\tconst destPos = new Float32Array(2);\n\t\t\t\t\t\taNode.getConnectionPos(!isInput, acceptingNode.connection_slot_index, destPos);\n\t\t\t\t\t\tctx.moveTo(pos[0], pos[1]);\n\n\t\t\t\t\t\tctx.lineTo(destPos[0], destPos[1]);\n\t\t\t\t\t\tctx.stroke();\n\t\t\t\t\t\tctx.closePath();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst slotColor =\n\t\t\t\t\t\t\tthis.canvas.default_connection_color_byType[acceptingNode.connection.type]\n\t\t\t\t\t\t\t|| this.canvas.default_connection_color.input_on;\n\n\t\t\t\t\t\tctx.fillStyle = slotColor || this.canvas.default_connection_color.input_on;\n\t\t\t\t\t\tctx.beginPath();\n\n\t\t\t\t\t\tctx.arc(linkPos[0], linkPos[1], 4 * scale, 0, Math.PI * 2);\n\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\tctx.closePath();\n\t\t\t\t\t}\n\n\t\t\t\t\tlinkPos[1] += LiteGraph.NODE_SLOT_HEIGHT * scale;\n\t\t\t\t\treturn false;\n\t\t\t\t});\n\n\t\t\t\tif (boxRect) {\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.fillStyle = this.boxBackground;\n\t\t\t\t\tconst oldAlpha = ctx.globalAlpha;\n\t\t\t\t\tctx.globalAlpha = this.boxAlpha;\n\t\t\t\t\tctx.roundRect(\n\t\t\t\t\t\tboxRect[0],\n\t\t\t\t\t\tboxRect[1],\n\t\t\t\t\t\tboxRect[2],\n\t\t\t\t\t\tboxRect[3],\n\t\t\t\t\t\t5,\n\t\t\t\t\t);\n\t\t\t\t\tctx.fill();\n\t\t\t\t\tctx.closePath();\n\t\t\t\t\tctx.globalAlpha = oldAlpha;\n\t\t\t\t}\n\n\t\t\t\tctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;\n\t\t\t\ttextsToDraw.filter((textToDraw) => {\n\t\t\t\t\tctx.textAlign = textToDraw.textAlign;\n\t\t\t\t\tctx.fillText(textToDraw.acceptingText, textToDraw.x, textToDraw.y);\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\n\t\t\t\tctx.font = oldFont;\n\t\t\t}\n\t\t\tctx.restore();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "docs/links/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": "docs/links/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": "docs/quick_conn.html",
    "content": "<html>\n<head>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"links/litegraph.css\">\n\t<script type=\"text/javascript\" src=\"links/litegraph.js\"></script>\n\t<style type='text/css'>\nbody {\n\twidth: 100%;\n\theight: 100%;\n\tmargin: 0px;\n}\n\t</style>\n</head>\n<body>\n<canvas id='mycanvas' width='1024' height='720' style='border: 1px solid'></canvas>\n<script type='module' src='quick_conn.js'></script>\n<div class='test-info' style='display: none'>\n\t<ul>\n\t\t<li>Test quick connect from input to output</li>\n\t\t<li>Output to Input</li>\n\t\t<li>Scroll to right and check lines</li>\n\t\t<li></li>\n\t</ul>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "docs/quick_conn.js",
    "content": "/* global LGraphCanvas */\n/* global LGraph */\n/* global LiteGraph */\n/* eslint camelcase:0 */\n/* eslint import/extensions: [0, {  <js>: \"always\"  }] */\n\nimport { QuickConnection } from './QuickConnection.js';\nimport { CircuitBoardLines } from './CircuitBoardLines.js';\n\n// LiteGraph.alt_drag_do_clone_nodes=true;\n\nconst circuitBoardLines = new CircuitBoardLines();\ncircuitBoardLines.init();\n\nconst quickConnection = new QuickConnection();\nquickConnection.init();\nconst graph = new LGraph();\nconst mycanvas = document.getElementById('mycanvas');\nmycanvas.width = window.innerWidth;\nmycanvas.height = window.innerHeight;\nconst canvas = new LGraphCanvas(mycanvas, graph);\nwindow.canvas = canvas;\n// canvas.links_render_mode = LiteGraph.CIRCUITBOARD_LINK;\n\nquickConnection.initListeners(canvas);\ncircuitBoardLines.initOverrides(canvas);\n\nconst params = new URLSearchParams(window.location.search.substring(1));\nif (!params.get('nodebug')) {\n\tcircuitBoardLines.debug = true;\n\tdocument.querySelector('.test-info').style.display = 'block';\n}\n\nfunction addNodes() {\n\tconst node_const = LiteGraph.createNode('basic/const');\n\tnode_const.pos = [770, 200];\n\tgraph.add(node_const);\n\tnode_const.setValue(4.5);\n\tnode_const.collapse();\n\n\tconst node_watch = LiteGraph.createNode('basic/watch');\n\tnode_watch.pos = [580, 500];\n\tgraph.add(node_watch);\n\tnode_const.connect(0, node_watch, 0);\n\n\tconst node_mathCompare = LiteGraph.createNode('math/compare');\n\tnode_mathCompare.pos = [800, 400];\n\tgraph.add(node_mathCompare);\n\tnode_const.connect(0, node_mathCompare, 0);\n\n\tconst node_string = LiteGraph.createNode('basic/string');\n\tnode_string.pos = [200, 230];\n\tgraph.add(node_string);\n\n\tconst node_panel = LiteGraph.createNode('widget/panel');\n\tnode_panel.pos = [440, 150];\n\tnode_panel.size = [200, 240];\n\tgraph.add(node_panel);\n\n\tconst node_compare = LiteGraph.createNode('string/compare');\n\tnode_compare.pos = [700, 300];\n\tgraph.add(node_compare);\n\tnode_string.connect(0, node_compare, 0);\n\n\tconst node_combo1 = LiteGraph.createNode('widget/combo');\n\tnode_combo1.pos = [100, 400];\n\tgraph.add(node_combo1);\n\n\tconst node_combo2 = LiteGraph.createNode('widget/combo');\n\tnode_combo2.pos = [90, 500];\n\tgraph.add(node_combo2);\n\n\tconst node_concat = LiteGraph.createNode('string/concatenate');\n\tnode_concat.pos = [150, 620];\n\tgraph.add(node_concat);\n\tnode_combo1.connect(0, node_concat, 0);\n\tnode_combo2.connect(0, node_concat, 1);\n\n\tconst node_bypass = LiteGraph.createNode('math/bypass');\n\tnode_bypass.pos = [780, 500];\n\tgraph.add(node_bypass);\n\n\tconst node_noise = LiteGraph.createNode('math/noise');\n\tnode_noise.pos = [720, 600];\n\tgraph.add(node_noise);\n\n\tconst node_array = LiteGraph.createNode('basic/array');\n\tnode_array.pos = [600, 700];\n\tgraph.add(node_array);\n\tnode_array.connect(0, node_bypass, 0);\n\tnode_array.connect(1, node_noise, 0);\n}\n\nfunction addNodes2() {\n\tconst node_const = LiteGraph.createNode('basic/const');\n\tnode_const.pos = [1350, 80];\n\tgraph.add(node_const);\n\n\tconst node_watch = LiteGraph.createNode('basic/watch');\n\tnode_watch.pos = [1450, 200];\n\tgraph.add(node_watch);\n\tnode_const.connect(0, node_watch, 0);\n\n\tconst node_const2 = LiteGraph.createNode('basic/const');\n\tnode_const2.pos = [1200, 180];\n\tgraph.add(node_const2);\n\n\tconst node_watch2 = LiteGraph.createNode('basic/watch');\n\tnode_watch2.pos = [1700, 180];\n\tgraph.add(node_watch2);\n\tnode_const2.connect(0, node_watch2, 0);\n}\n\naddNodes();\naddNodes2();\n\ngraph.start();\n"
  },
  {
    "path": "example/comfyui_quick_conn.html",
    "content": "<html>\n<head>\n\t<!--\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"litegraph.css\">\n\t<script type=\"text/javascript\" src=\"litegraph.js\"></script>\n\t-->\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"links/comfyui_litegraph.css\">\n\t<style type='text/css'>\nbody {\n\twidth: 100%;\n\theight: 100%;\n\tmargin: 0px;\n}\n\t</style>\n</head>\n<body>\n<canvas id='mycanvas' width='1024' height='720' style='border: 1px solid'></canvas>\n<script type='module' src='comfyui_quick_conn.js'></script>\n</body>\n</html>\n"
  },
  {
    "path": "example/comfyui_quick_conn.js",
    "content": "/* eslint camelcase:0 */\n/* eslint no-else-return:0 */\n/* eslint max-classes-per-file:0 */\n\nimport {\n\tLGraphNode,\n\tLGraph,\n\tLiteGraph,\n\tLGraphCanvas,\n} from './links/comfyui_litegraph.js';\nimport { QuickConnection } from './QuickConnection.js';\nimport { CircuitBoardLines } from './CircuitBoardLines.js';\n\nwindow.LGraph = LGraph;\nwindow.LiteGraph = LiteGraph;\nwindow.LGraphCanvas = LGraphCanvas;\n\n// LiteGraph.alt_drag_do_clone_nodes=true;\n//\n(function run() {\n\t// Watch a value in the editor\n\tclass Watch extends LGraphNode {\n\t\tconstructor() {\n\t\t\tsuper();\n\t\t\tthis.size = [60, 30];\n\t\t\tthis.addInput('value', 0, { label: '' });\n\t\t\tthis.value = 0;\n\t\t\tthis.title = 'Watch';\n\t\t\tthis.desc = 'Show value of input';\n\t\t}\n\n\t\tonExecute() {\n\t\t\tif (this.inputs[0]) {\n\t\t\t\tthis.value = this.getInputData(0);\n\t\t\t}\n\t\t}\n\n\t\tgetTitle() {\n\t\t\tif (this.flags.collapsed) {\n\t\t\t\treturn this.inputs[0].label;\n\t\t\t}\n\t\t\treturn this.title;\n\t\t}\n\n\t\ttoString(o) {\n\t\t\tif (o == null) {\n\t\t\t\treturn 'null';\n\t\t\t} else if (o.constructor === Number) {\n\t\t\t\treturn o.toFixed(3);\n\t\t\t} else if (o.constructor === Array) {\n\t\t\t\tlet str = '[';\n\t\t\t\tfor (let i = 0; i < o.length; ++i) {\n\t\t\t\t\tstr += this.toString(o[i]) + (i + 1 !== o.length ? ',' : '');\n\t\t\t\t}\n\t\t\t\tstr += ']';\n\t\t\t\treturn str;\n\t\t\t} else {\n\t\t\t\treturn String(o);\n\t\t\t}\n\t\t}\n\n\t\tonDrawBackground(/* ctx */) {\n\t\t\t// show the current value\n\t\t\tthis.inputs[0].label = this.toString(this.value);\n\t\t}\n\t}\n\n\tLiteGraph.registerNodeType('basic/watch', Watch);\n\n\t// Constant\n\tclass ConstantNumber extends LGraphNode {\n\t\tconstructor() {\n\t\t\tsuper();\n\t\t\tthis.addOutput('value', 'number');\n\t\t\tthis.addProperty('value', 1.0);\n\t\t\tthis.widget = this.addWidget('number', 'value', 1, 'value');\n\t\t\tthis.widgets_up = true;\n\t\t\tthis.size = [180, 30];\n\t\t\tthis.title = 'Const Number';\n\t\t\tthis.desc = 'Constant number';\n\t\t}\n\n\t\tonExecute() {\n\t\t\tthis.setOutputData(0, parseFloat(this.properties.value));\n\t\t}\n\n\t\tgetTitle() {\n\t\t\tif (this.flags.collapsed) {\n\t\t\t\treturn this.properties.value;\n\t\t\t}\n\t\t\treturn this.title;\n\t\t}\n\n\t\tsetValue(v) {\n\t\t\tthis.setProperty('value', v);\n\t\t}\n\n\t\tonDrawBackground(/* ctx */) {\n\t\t\t// show the current value\n\t\t\tthis.outputs[0].label = this.properties.value.toFixed(3);\n\t\t}\n\t}\n\n\tLiteGraph.registerNodeType('basic/const', ConstantNumber);\n}());\n\nconst circuitBoardLines = new CircuitBoardLines();\ncircuitBoardLines.init();\n\nconst quickConnection = new QuickConnection();\nquickConnection.init();\nconst graph = new LGraph();\nconst canvas = new LGraphCanvas('mycanvas', graph);\nwindow.canvas = canvas;\n\nquickConnection.initListeners(canvas);\ncircuitBoardLines.initOverrides(canvas);\ncircuitBoardLines.debug = true;\nconst node_const = LiteGraph.createNode('basic/const');\nnode_const.pos = [740, 100];\ngraph.add(node_const);\nnode_const.setValue(4.5);\n\nconst node_watch = LiteGraph.createNode('basic/watch');\nnode_watch.pos = [680, 200];\ngraph.add(node_watch);\nnode_const.connect(0, node_watch, 0);\n\ngraph.start();\n"
  },
  {
    "path": "example/make_docs.sh",
    "content": "\nmkdir -p docs/links\ncp -auL example/quick_conn.* js/CircuitBoardLines.js js/QuickConnection.js docs/\ncp -auL example/links/litegraph.* docs/links/\n"
  },
  {
    "path": "example/quick_conn.html",
    "content": "<html>\n<head>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"links/litegraph.css\">\n\t<script type=\"text/javascript\" src=\"links/litegraph.js\"></script>\n\t<style type='text/css'>\nbody {\n\twidth: 100%;\n\theight: 100%;\n\tmargin: 0px;\n}\n\t</style>\n</head>\n<body>\n<canvas id='mycanvas' width='1024' height='720' style='border: 1px solid'></canvas>\n<script type='module' src='quick_conn.js'></script>\n<div class='test-info' style='display: none'>\n\t<ul>\n\t\t<li>Test quick connect from input to output</li>\n\t\t<li>Output to Input</li>\n\t\t<li>Scroll to right and check lines</li>\n\t\t<li></li>\n\t</ul>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "example/quick_conn.js",
    "content": "/* global LGraphCanvas */\n/* global LGraph */\n/* global LiteGraph */\n/* eslint camelcase:0 */\n\nimport { QuickConnection } from './QuickConnection.js';\nimport { CircuitBoardLines } from './CircuitBoardLines.js';\n\n// LiteGraph.alt_drag_do_clone_nodes=true;\n\nconst circuitBoardLines = new CircuitBoardLines();\ncircuitBoardLines.init();\n\nconst quickConnection = new QuickConnection();\nquickConnection.init();\nconst graph = new LGraph();\nconst mycanvas = document.getElementById('mycanvas');\nmycanvas.width = window.innerWidth;\nmycanvas.height = window.innerHeight;\nconst canvas = new LGraphCanvas(mycanvas, graph);\nwindow.canvas = canvas;\n// canvas.links_render_mode = LiteGraph.CIRCUITBOARD_LINK;\n\nquickConnection.initListeners(canvas);\ncircuitBoardLines.initOverrides(canvas);\n\nconst params = new URLSearchParams(window.location.search.substring(1));\nif (!params.get('nodebug')) {\n\tcircuitBoardLines.debug = true;\n\tdocument.querySelector('.test-info').style.display = 'block';\n}\n\nfunction addNodes() {\n\tconst node_const = LiteGraph.createNode('basic/const');\n\tnode_const.pos = [770, 200];\n\tgraph.add(node_const);\n\tnode_const.setValue(4.5);\n\tnode_const.collapse();\n\n\tconst node_watch = LiteGraph.createNode('basic/watch');\n\tnode_watch.pos = [580, 500];\n\tgraph.add(node_watch);\n\tnode_const.connect(0, node_watch, 0);\n\n\tconst node_mathCompare = LiteGraph.createNode('math/compare');\n\tnode_mathCompare.pos = [800, 400];\n\tgraph.add(node_mathCompare);\n\tnode_const.connect(0, node_mathCompare, 0);\n\n\tconst node_string = LiteGraph.createNode('basic/string');\n\tnode_string.pos = [200, 230];\n\tgraph.add(node_string);\n\n\tconst node_panel = LiteGraph.createNode('widget/panel');\n\tnode_panel.pos = [440, 150];\n\tnode_panel.size = [200, 240];\n\tgraph.add(node_panel);\n\n\tconst node_compare = LiteGraph.createNode('string/compare');\n\tnode_compare.pos = [700, 300];\n\tgraph.add(node_compare);\n\tnode_string.connect(0, node_compare, 0);\n\n\tconst node_combo1 = LiteGraph.createNode('widget/combo');\n\tnode_combo1.pos = [100, 400];\n\tgraph.add(node_combo1);\n\n\tconst node_combo2 = LiteGraph.createNode('widget/combo');\n\tnode_combo2.pos = [90, 500];\n\tgraph.add(node_combo2);\n\n\tconst node_concat = LiteGraph.createNode('string/concatenate');\n\tnode_concat.pos = [150, 620];\n\tgraph.add(node_concat);\n\tnode_combo1.connect(0, node_concat, 0);\n\tnode_combo2.connect(0, node_concat, 1);\n\n\tconst node_bypass = LiteGraph.createNode('math/bypass');\n\tnode_bypass.pos = [780, 500];\n\tgraph.add(node_bypass);\n\n\tconst node_noise = LiteGraph.createNode('math/noise');\n\tnode_noise.pos = [720, 600];\n\tgraph.add(node_noise);\n\n\tconst node_array = LiteGraph.createNode('basic/array');\n\tnode_array.pos = [600, 700];\n\tgraph.add(node_array);\n\tnode_array.connect(0, node_bypass, 0);\n\tnode_array.connect(1, node_noise, 0);\n}\n\nfunction addNodes2() {\n\tconst node_const = LiteGraph.createNode('basic/const');\n\tnode_const.pos = [1350, 80];\n\tgraph.add(node_const);\n\n\tconst node_watch = LiteGraph.createNode('basic/watch');\n\tnode_watch.pos = [1450, 200];\n\tgraph.add(node_watch);\n\tnode_const.connect(0, node_watch, 0);\n\n\tconst node_const2 = LiteGraph.createNode('basic/const');\n\tnode_const2.pos = [1200, 180];\n\tgraph.add(node_const2);\n\n\tconst node_watch2 = LiteGraph.createNode('basic/watch');\n\tnode_watch2.pos = [1700, 180];\n\tgraph.add(node_watch2);\n\tnode_const2.connect(0, node_watch2, 0);\n}\n\naddNodes();\naddNodes2();\n\ngraph.start();\n"
  },
  {
    "path": "example/run_example.sh",
    "content": "\n\nmkdir -p example/links\npushd example/links\nln -s ../../node_modules/@comfyorg/litegraph/dist/css/litegraph.css comfyui_litegraph.css\nln -s ../../node_modules/@comfyorg/litegraph/dist/litegraph.es.js comfyui_litegraph.js\nln -s ../../node_modules/litegraph.js/css/litegraph.css litegraph.css\nln -s ../../node_modules/litegraph.js/build/litegraph.js litegraph.js\npopd\necho \"Visit... http://localhost:8000/example/quick_conn.html\"\npython3 -m http.server\n\n"
  },
  {
    "path": "imgs/SpeedUpMp4ToGif.sh",
    "content": "\nffmpeg -y -i \"$1\" -filter_complex \"[0:v]fps=10,split[a][b];[b]palettegen[p];[a][p]paletteuse,setpts=0.5*PTS[v]\" -map '[v]' \"$2\"\n\n"
  },
  {
    "path": "js/CircuitBoardLines.js",
    "content": "/* eslint max-classes-per-file: 0 */\n/* eslint no-tabs: 0 */\n/* eslint no-underscore-dangle:0 */\n/* eslint prefer-rest-params:0 */\n/* eslint curly:0 */\n/* eslint no-plusplus:0 */\n/* global LiteGraph */\n/* global LGraphCanvas */\n\n/**\n * @preserve\n * Fast, destructive implemetation of Liang-Barsky line clipping algorithm.\n * It clips a 2D segment by a rectangle.\n * @author Alexander Milevski <info@w8r.name>\n * @license MIT\n */\nconst EPSILON = 1e-6;\nconst INSIDE = 1;\nconst OUTSIDE = 0;\n\nfunction clipT(num, denom, c) {\n\tconst tE = c[0], tL = c[1];\n\tif (Math.abs(denom) < EPSILON)\n\t\treturn num < 0;\n\tconst t = num / denom;\n\tif (denom > 0) {\n\t\tif (t > tL)\n\t\t\treturn 0;\n\t\tif (t > tE)\n\t\t\tc[0] = t;\n\t} else {\n\t\tif (t < tE)\n\t\t\treturn 0;\n\t\tif (t < tL)\n\t\t\tc[1] = t;\n\t}\n\treturn 1;\n}\n/**\n * Check if a line is going over a box(node)\n * @param\t{Point} a\n * @param\t{Point} b\n * @param\t{BoundingBox} box [xmin, ymin, xmax, ymax]\n * @param\t{Point?} [da]\n * @param\t{Point?} [db]\n * @return {number}\n */\nfunction liangBarsky(a, b, box, da, db) {\n\tconst x1 = a[0], y1 = a[1];\n\tconst x2 = b[0], y2 = b[1];\n\tconst dx = x2 - x1;\n\tconst dy = y2 - y1;\n\tif (da === undefined || db === undefined) {\n\t\tda = a;\n\t\tdb = b;\n\t} else {\n\t\tda[0] = a[0];\n\t\tda[1] = a[1];\n\t\tdb[0] = b[0];\n\t\tdb[1] = b[1];\n\t}\n\tif (Math.abs(dx) < EPSILON &&\n\t\tMath.abs(dy) < EPSILON &&\n\t\tx1 >= box[0] &&\n\t\tx1 <= box[2] &&\n\t\ty1 >= box[1] &&\n\t\ty1 <= box[3]) {\n\t\treturn INSIDE;\n\t}\n\tconst c = [0, 1];\n\tif (clipT(box[0] - x1, dx, c) &&\n\t\tclipT(x1 - box[2], -dx, c) &&\n\t\tclipT(box[1] - y1, dy, c) &&\n\t\tclipT(y1 - box[3], -dy, c)) {\n\t\tconst tE = c[0], tL = c[1];\n\t\tif (tL < 1) {\n\t\t\tdb[0] = x1 + tL * dx;\n\t\t\tdb[1] = y1 + tL * dy;\n\t\t}\n\t\tif (tE > 0) {\n\t\t\tda[0] += tE * dx;\n\t\t\tda[1] += tE * dy;\n\t\t}\n\t\treturn INSIDE;\n\t}\n\treturn OUTSIDE;\n}\n\nclass MapLinks {\n\tconstructor(canvas, config) {\n\t\tthis.canvas = canvas;\n\t\tthis.nodesByRight = [];\n\t\tthis.nodesById = [];\n\t\tthis.lastPathId = 10000000;\n\t\tthis.paths = [];\n\t\tthis.config = config;\n\t\tthis.maxDirectLineDistance = Number.MAX_SAFE_INTEGER;\n\t\tthis.debug = false;\n\t}\n\n\tisInsideNode(xy) {\n\t\tfor (let i = 0; i < this.nodesByRight.length; ++i) {\n\t\t\tconst nodeI = this.nodesByRight[i];\n\t\t\tif (nodeI.node.isPointInside(xy[0], xy[1])) {\n\t\t\t\treturn nodeI.node;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t// find which node is in the way of the output to input line.\n\tfindClippedNode(outputXY, inputXY) {\n\t\tlet closestDistance = Number.MAX_SAFE_INTEGER;\n\t\tlet closest = null;\n\n\t\tfor (let i = 0; i < this.nodesByRight.length; ++i) {\n\t\t\tconst node = this.nodesByRight[i];\n\t\t\tconst clipA = [-1, -1]; // outputXY.slice();\n\t\t\tconst clipB = [-1, -1]; // inputXY.slice();\n\t\t\tconst clipped = liangBarsky(\n\t\t\t\toutputXY,\n\t\t\t\tinputXY,\n\t\t\t\tnode.area,\n\t\t\t\tclipA,\n\t\t\t\tclipB,\n\t\t\t);\n\n\t\t\tif (clipped === INSIDE) {\n\t\t\t\tconst centerX = (node.area[0] + ((node.area[2] - node.area[0]) / 2));\n\t\t\t\tconst centerY = (node.area[1] + ((node.area[3] - node.area[1]) / 2));\n\t\t\t\tconst dist = Math.sqrt(((centerX - outputXY[0]) ** 2) + ((centerY - outputXY[1]) ** 2));\n\t\t\t\tif (dist < closestDistance) {\n\t\t\t\t\tclosest = {\n\t\t\t\t\t\tstart: clipA,\n\t\t\t\t\t\tend: clipB,\n\t\t\t\t\t\tnode,\n\t\t\t\t\t};\n\t\t\t\t\tclosestDistance = dist;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { clipped: closest, closestDistance };\n\t}\n\n\ttestPath(path) {\n\t\tconst len1 = (path.length - 1);\n\t\tfor (let p = 0; p < len1; ++p) {\n\t\t\tconst { clipped } = this.findClippedNode(path[p], path[p + 1]);\n\t\t\tif (clipped) {\n\t\t\t\treturn clipped;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tmapFinalLink(outputXY, inputXY) {\n\t\tconst { clipped } = this.findClippedNode(outputXY, inputXY);\n\t\tif (!clipped) {\n\t\t\tconst dist = Math.sqrt(((outputXY[0] - inputXY[0]) ** 2) + ((outputXY[1] - inputXY[1]) ** 2));\n\t\t\tif (dist < this.maxDirectLineDistance) {\n\t\t\t\t// direct, nothing blocking us\n\t\t\t\treturn { path: [outputXY, inputXY] };\n\t\t\t}\n\t\t}\n\n\t\tconst horzDistance = inputXY[0] - outputXY[0];\n\t\tconst vertDistance = inputXY[1] - outputXY[1];\n\t\tconst horzDistanceAbs = Math.abs(horzDistance);\n\t\tconst vertDistanceAbs = Math.abs(vertDistance);\n\n\t\tif (horzDistanceAbs > vertDistanceAbs) {\n\t\t\t// we should never go left anyway,\n\t\t\t// because input slot is always on left and output is always on right\n\t\t\tconst goingLeft = inputXY[0] < outputXY[0];\n\t\t\tconst pathStraight45 = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[inputXY[0] - (goingLeft ? -vertDistanceAbs : vertDistanceAbs), outputXY[1]],\n\t\t\t\t[inputXY[0], inputXY[1]],\n\t\t\t];\n\t\t\t// __/\n\t\t\t//\n\t\t\t// __\n\t\t\t//   \\\n\t\t\tif (!this.testPath(pathStraight45)) {\n\t\t\t\treturn { path: pathStraight45 };\n\t\t\t}\n\n\t\t\tconst path45Straight = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[outputXY[0] + (goingLeft ? -vertDistanceAbs : vertDistanceAbs), inputXY[1]],\n\t\t\t\t[inputXY[0], inputXY[1]],\n\t\t\t];\n\t\t\t// \\__\n\t\t\t//\n\t\t\t//  __\n\t\t\t// /\n\t\t\tif (!this.testPath(path45Straight)) {\n\t\t\t\treturn { path: path45Straight };\n\t\t\t}\n\t\t} else {\n\t\t\t// move vert\n\t\t\tconst goingUp = inputXY[1] < outputXY[1];\n\t\t\tconst pathStraight45 = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[outputXY[0], inputXY[1] + (goingUp ? horzDistanceAbs : -horzDistanceAbs)],\n\t\t\t\t[inputXY[0], inputXY[1]],\n\t\t\t];\n\t\t\t// |\n\t\t\t// |\n\t\t\t//  \\\n\t\t\tif (!this.testPath(pathStraight45)) {\n\t\t\t\treturn { path: pathStraight45 };\n\t\t\t}\n\n\t\t\tconst path45Straight = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[inputXY[0], outputXY[1] - (goingUp ? horzDistanceAbs : -horzDistanceAbs)],\n\t\t\t\t[inputXY[0], inputXY[1]],\n\t\t\t];\n\t\t\t// \\\n\t\t\t//  |\n\t\t\t//  |\n\t\t\tif (!this.testPath(path45Straight)) {\n\t\t\t\treturn { path: path45Straight };\n\t\t\t}\n\t\t}\n\n\t\tconst path90Straight = [\n\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t[outputXY[0], inputXY[1]],\n\t\t\t[inputXY[0], inputXY[1]],\n\t\t];\n\t\t// |_\n\t\tconst clippedVert = this.testPath(path90Straight);\n\t\tif (!clippedVert) {\n\t\t\treturn { path: path90Straight };\n\t\t}\n\n\t\tconst pathStraight90 = [\n\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t[inputXY[0], outputXY[1]],\n\t\t\t[inputXY[0], inputXY[1]],\n\t\t];\n\t\t// _\n\t\t//  |\n\t\t//\n\t\t// _|\n\t\tconst clippedHorz = this.testPath(pathStraight90);\n\t\tif (!clippedHorz) {\n\t\t\t// add to lines area in destination node?\n\t\t\t// targetNodeInfo.linesArea[0] -= this.lineSpace;\n\t\t\treturn { path: pathStraight90 };\n\t\t}\n\t\treturn {\n\t\t\tclippedHorz,\n\t\t\tclippedVert,\n\t\t};\n\t}\n\n\tmapLink(outputXY, inputXY, targetNodeInfo, isBlocked /* , lastDirection */) {\n\t\tconst { clippedHorz, clippedVert, path } = this.mapFinalLink(outputXY, inputXY);\n\t\tif (path) {\n\t\t\treturn path;\n\t\t}\n\n\t\tconst horzDistance = inputXY[0] - outputXY[0];\n\t\tconst vertDistance = inputXY[1] - outputXY[1];\n\t\tconst horzDistanceAbs = Math.abs(horzDistance);\n\t\tconst vertDistanceAbs = Math.abs(vertDistance);\n\n\t\tlet blockedNodeId;\n\t\t// let blockedArea;\n\t\tlet pathAvoidNode;\n\t\tlet lastPathLocation;\n\t\tlet linesArea;\n\n\t\tlet thisDirection = null;\n\t\t// if (lastDirection !== 'horz' && horzDistanceAbs > vertDistanceAbs) {\n\t\tif (horzDistanceAbs > vertDistanceAbs) {\n\t\t\t// horz then vert to avoid blocking node\n\t\t\tblockedNodeId = clippedHorz.node.node.id;\n\t\t\t// blockedArea = clippedHorz.node.area;\n\t\t\tlinesArea = clippedHorz.node.linesArea;\n\t\t\tconst horzEdge = horzDistance <= 0\n\t\t\t\t? (linesArea[2])\n\t\t\t\t: (linesArea[0] - 1);\n\t\t\tpathAvoidNode = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[horzEdge, outputXY[1]],\n\t\t\t];\n\n\t\t\tif (horzDistance <= 0) {\n\t\t\t\tlinesArea[2] += this.config.lineSpace;\n\t\t\t} else {\n\t\t\t\tlinesArea[0] -= this.config.lineSpace;\n\t\t\t}\n\n\t\t\tconst vertDistanceViaBlockTop =\n\t\t\t\tMath.abs(inputXY[1] - linesArea[1]) +\n\t\t\t\tMath.abs(linesArea[1] - outputXY[1]);\n\t\t\tconst vertDistanceViaBlockBottom =\n\t\t\t\tMath.abs(inputXY[1] - linesArea[3]) +\n\t\t\t\tMath.abs(linesArea[3] - outputXY[1]);\n\n\t\t\tlastPathLocation = [\n\t\t\t\thorzEdge,\n\t\t\t\tvertDistanceViaBlockTop <= vertDistanceViaBlockBottom ?\n\t\t\t\t\t(linesArea[1])\n\t\t\t\t\t: (linesArea[3]),\n\t\t\t];\n\t\t\tconst unblockNotPossible1 = this.testPath([...pathAvoidNode, lastPathLocation]);\n\t\t\tif (unblockNotPossible1) {\n\t\t\t\tlastPathLocation = [\n\t\t\t\t\thorzEdge,\n\t\t\t\t\tvertDistanceViaBlockTop > vertDistanceViaBlockBottom ?\n\t\t\t\t\t\t(linesArea[1])\n\t\t\t\t\t\t: (linesArea[3]),\n\t\t\t\t];\n\t\t\t}\n\t\t\tif (lastPathLocation[1] < outputXY[1]) {\n\t\t\t\tlinesArea[1] -= this.config.lineSpace;\n\t\t\t\tlastPathLocation[1] -= 1;\n\t\t\t} else {\n\t\t\t\tlinesArea[3] += this.config.lineSpace;\n\t\t\t\tlastPathLocation[1] += 1;\n\t\t\t}\n\t\t\tthisDirection = 'vert';\n\t\t// } else if (lastDirection !== 'vert') {\n\t\t} else {\n\t\t\t// vert then horz to avoid blocking node\n\t\t\tblockedNodeId = clippedVert.node.node.id;\n\t\t\t// blockedArea = clippedVert.node.area;\n\t\t\tlinesArea = clippedVert.node.linesArea;\n\t\t\t// Special +/- 1 here because of the way it's calculated\n\t\t\tconst vertEdge =\n\t\t\t\tvertDistance <= 0\n\t\t\t\t\t? (linesArea[3] + 1)\n\t\t\t\t\t: (linesArea[1] - 1);\n\t\t\tpathAvoidNode = [\n\t\t\t\t[outputXY[0], outputXY[1]],\n\t\t\t\t[outputXY[0], vertEdge],\n\t\t\t];\n\t\t\tif (vertDistance <= 0) {\n\t\t\t\tlinesArea[3] += this.config.lineSpace;\n\t\t\t} else {\n\t\t\t\tlinesArea[1] -= this.config.lineSpace;\n\t\t\t}\n\n\t\t\tconst horzDistanceViaBlockLeft =\n\t\t\t\tMath.abs(inputXY[0] - linesArea[0]) +\n\t\t\t\tMath.abs(linesArea[0] - outputXY[0]);\n\t\t\tconst horzDistanceViaBlockRight =\n\t\t\t\tMath.abs(inputXY[0] - linesArea[2]) +\n\t\t\t\tMath.abs(linesArea[2] - outputXY[0]);\n\n\t\t\tlastPathLocation = [\n\t\t\t\thorzDistanceViaBlockLeft <= horzDistanceViaBlockRight ?\n\t\t\t\t\t(linesArea[0] - 1)\n\t\t\t\t\t: (linesArea[2]),\n\t\t\t\tvertEdge,\n\t\t\t];\n\t\t\tconst unblockNotPossible1 = this.testPath([...pathAvoidNode, lastPathLocation]);\n\t\t\tif (unblockNotPossible1) {\n\t\t\t\tlastPathLocation = [\n\t\t\t\t\thorzDistanceViaBlockLeft > horzDistanceViaBlockRight ?\n\t\t\t\t\t\t(linesArea[0])\n\t\t\t\t\t\t: (linesArea[2]),\n\t\t\t\t\tvertEdge,\n\t\t\t\t];\n\t\t\t}\n\t\t\tif (lastPathLocation[0] < outputXY[0]) {\n\t\t\t\tlinesArea[0] -= this.config.lineSpace;\n\t\t\t\t// lastPathLocation[0] -= 1; //this.lineSpace;\n\t\t\t} else {\n\t\t\t\tlinesArea[2] += this.config.lineSpace;\n\t\t\t\t// lastPathLocation[0] += 1; //this.lineSpace;\n\t\t\t}\n\t\t\tthisDirection = 'horz';\n\t\t\t//\t\t} else {\n\t\t\t//\t\t\tconsole.log('blocked will not go backwards', outputXY, inputXY);\n\t\t\t//\t\t\treturn [outputXY, inputXY];\n\t\t}\n\n\t\t// console.log('is blocked check',isBlocked, blockedNodeId);\n\t\tif (isBlocked[blockedNodeId] > 3) {\n\t\t\t// Blocked too many times, let's return the direct path\n\t\t\tconsole.log('CircuitBoardLines: Too many blocked, node id:', blockedNodeId, 'output', outputXY, 'input', inputXY);\n\t\t\tisBlocked.blocked = true;\n\t\t\treturn [outputXY, inputXY];\n\t\t}\n\t\tif (isBlocked[blockedNodeId])\n\t\t\t++isBlocked[blockedNodeId];\n\t\telse\n\t\t\tisBlocked[blockedNodeId] = 1;\n\t\t// console.log('pathavoid', pathAvoidNode);\n\t\tconst nextPath = this.mapLink(\n\t\t\tlastPathLocation,\n\t\t\tinputXY,\n\t\t\ttargetNodeInfo,\n\t\t\tisBlocked,\n\t\t\tthisDirection,\n\t\t);\n\t\treturn [...pathAvoidNode, lastPathLocation, ...nextPath.slice(1)];\n\t}\n\n\t// expand the area around a node that we should not draw on\n\texpandSourceNodeLinesArea(sourceNodeInfo, path) {\n\t\tif (path.length < 3) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst linesArea = sourceNodeInfo.linesArea;\n\t\tif (path[1][0] === path[2][0]) {\n\t\t\t// first link is going vertical\n\t\t\t// while (path[1][0] > linesArea[2])\n\t\t\tlinesArea[2] += this.config.lineSpace;\n\t\t}\n\t\treturn true;\n\t}\n\n\t// expand left side of target node if we're going up there vertically.\n\texpandTargetNodeLinesArea(targetNodeInfo, path) {\n\t\tif (path.length < 2) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst linesArea = targetNodeInfo.linesArea;\n\t\tconst path2Len = path.length - 2;\n\t\tif (path[path2Len - 1][0] === path[path2Len][0]) {\n\t\t\t// first link is going vertical\n\t\t\t// while (path[path2Len][0] < linesArea[0])\n\t\t\tlinesArea[0] -= this.config.lineSpace;\n\t\t}\n\t\treturn true;\n\t}\n\n\t// get the node on this x,y spot\n\tgetNodeOnPos(xy) {\n\t\tfor (let i = 0; i < this.nodesByRight.length; ++i) {\n\t\t\tconst nodeI = this.nodesByRight[i];\n\t\t\tconst { linesArea } = nodeI;\n\t\t\tif (xy[0] >= linesArea[0]\n\t\t\t\t&& xy[1] >= linesArea[1]\n\t\t\t\t&& xy[0] < linesArea[2]\n\t\t\t\t&& xy[1] < linesArea[3]\n\t\t\t) {\n\t\t\t\treturn nodeI;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t// Find out how to draw the links\n\tmapLinks(nodesByExecution) {\n\t\tconst graphLinks = this.canvas.graph.links;\n\t\tif (!graphLinks) {\n\t\t\tconsole.error('Missing graph.links', this.canvas.graph);\n\t\t\treturn;\n\t\t}\n\n\t\tconst startCalcTime = new Date().getTime();\n\t\tthis.links = [];\n\t\tthis.lastPathId = 1000000;\n\t\tthis.nodesByRight = [];\n\t\tthis.nodesById = {};\n\t\tthis.nodesByRight = nodesByExecution.map((node) => {\n\t\t\tconst barea = new Float32Array(4);\n\t\t\tnode.getBounding(barea);\n\t\t\tconst area = [\n\t\t\t\tbarea[0],\n\t\t\t\tbarea[1],\n\t\t\t\tbarea[0] + barea[2],\n\t\t\t\tbarea[1] + barea[3],\n\t\t\t];\n\t\t\tconst linesArea = Array.from(area);\n\t\t\t// new layout needs more spacing\n\t\t\tlinesArea[0] += this.config.nodeSpace[0];\n\t\t\tlinesArea[1] += this.config.nodeSpace[1];\n\t\t\tlinesArea[2] += this.config.nodeSpace[2];\n\t\t\tlinesArea[3] += this.config.nodeSpace[3];\n\t\t\tconst obj = {\n\t\t\t\tnode,\n\t\t\t\tarea,\n\t\t\t\tlinesArea,\n\t\t\t};\n\t\t\tthis.nodesById[node.id] = obj;\n\t\t\treturn obj;\n\t\t});\n\t\t//\n\t\t//\t\tthis.nodesByRight.sort(\n\t\t//\t\t\t(a, b) => (a.area[1]) - (b.area[1]),\n\t\t//\t\t);\n\n\t\tconst nodesByRightId = this.nodesByRight.reduce(\n\t\t\t(a, x) => {\n\t\t\t\ta[x.node.id] = x.node;\n\t\t\t\treturn a;\n\t\t\t},\n\t\t\t{},\n\t\t);\n\t\tthis.nodesByRight.filter((nodeI) => {\n\t\t\tconst { node } = nodeI;\n\t\t\tconst outputs = node.outputs;\n\t\t\tif (!outputs) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\toutputs.filter((output, slot) => {\n\t\t\t\tconst links = output.links;\n\t\t\t\tif (!links) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tconst outputXYConnection =\n\t\t\t\t\t(LiteGraph.vueNodesMode && node.getSlotPosition)\n\t\t\t\t\t\t? node.getSlotPosition(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t: node.getOutputPos(slot);\n\t\t\t\tconst outputNodeInfo = this.nodesById[node.id];\n\t\t\t\tlet outputXY = Array.from(outputXYConnection);\n\t\t\t\tlinks.filter((linkId) => {\n\t\t\t\t\toutputXY[0] = outputNodeInfo.linesArea[2];\n\t\t\t\t\tconst link = graphLinks[linkId];\n\t\t\t\t\tif (!link) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tlet targetNode = this.canvas.graph.getNodeById(link.target_id);\n\t\t\t\t\tif (!targetNode) {\n\t\t\t\t\t\t// maybe this is the in / out node in a subgraph\n\t\t\t\t\t\ttargetNode = nodesByRightId[link.target_id];\n\t\t\t\t\t}\n\t\t\t\t\tif (!targetNode) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst inputXYConnection =\n\t\t\t\t\t\t(LiteGraph.vueNodesMode && targetNode.getSlotPosition)\n\t\t\t\t\t\t\t? targetNode.getSlotPosition(\n\t\t\t\t\t\t\t\tlink.target_slot,\n\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: targetNode.getInputPos(link.target_slot);\n\t\t\t\t\tconst inputXY = Array.from(inputXYConnection);\n\t\t\t\t\tconst nodeInfo = this.nodesById[targetNode.id];\n\t\t\t\t\tinputXY[0] = nodeInfo.linesArea[0] - 1;\n\n\t\t\t\t\tconst inputBlockedByNode =\n\t\t\t\t\t\tthis.getNodeOnPos(inputXY);\n\t\t\t\t\tconst outputBlockedByNode =\n\t\t\t\t\t\tthis.getNodeOnPos(outputXY);\n\n\t\t\t\t\tlet path = null;\n\t\t\t\t\t// console.log('blocked', inputBlockedByNode, outputBlockedByNode,\n\t\t\t\t\t//\t'inputXY', inputXY, 'outputXY', outputXY);\n\t\t\t\t\tif (!inputBlockedByNode && !outputBlockedByNode) {\n\t\t\t\t\t\tconst isBlocked = {};\n\t\t\t\t\t\tconst pathFound = this.mapLink(outputXY, inputXY, nodeInfo, isBlocked, null);\n\n\t\t\t\t\t\tif (pathFound) {\n\t\t\t\t\t\t\t// remove duplicate dot at the end\n\t\t\t\t\t\t\twhile (pathFound.length >= 2) {\n\t\t\t\t\t\t\t\tconst lastPathPoint1 = pathFound[pathFound.length - 1];\n\t\t\t\t\t\t\t\tconst lastPathPoint2 = pathFound[pathFound.length - 2];\n\t\t\t\t\t\t\t\tif (lastPathPoint1[0] === lastPathPoint2[0]\n\t\t\t\t\t\t\t\t\t&& lastPathPoint1[1] === lastPathPoint2[1]\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tpathFound.pop();\n\t\t\t\t\t\t\t\t} else {\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// Draw a direct line when it's blocked (isBlocked.blocked)\n\t\t\t\t\t\tif (!isBlocked.blocked && pathFound && pathFound.length > 2) {\n\t\t\t\t\t\t\t// mapLink() may have expanded the linesArea,\n\t\t\t\t\t\t\t// lets put it back into the inputXY so the line is straight\n\t\t\t\t\t\t\tpath = [outputXYConnection, ...pathFound, inputXYConnection];\n\t\t\t\t\t\t\tthis.expandTargetNodeLinesArea(nodeInfo, path);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!path) {\n\t\t\t\t\t\tpath = [outputXYConnection, outputXY, inputXY, inputXYConnection];\n\t\t\t\t\t}\n\t\t\t\t\tthis.expandSourceNodeLinesArea(nodeI, path);\n\t\t\t\t\tthis.paths.push({\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tnode,\n\t\t\t\t\t\ttargetNode,\n\t\t\t\t\t\tslot,\n\t\t\t\t\t});\n\t\t\t\t\toutputXY = [\n\t\t\t\t\t\toutputXY[0] + this.config.lineSpace,\n\t\t\t\t\t\toutputXY[1],\n\t\t\t\t\t];\n\t\t\t\t\treturn false;\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t});\n\t\t\treturn false;\n\t\t});\n\t\tthis.lastCalculate = new Date().getTime();\n\t\tthis.lastCalcTime = this.lastCalculate - startCalcTime;\n\t\tif (this.lastCalcTime > 30000) {\n\t\t\tthis.lastCalcTime = 30000; // might have paused in debugger\n\t\t}\n\n\t\tif (this.debug)\n\t\t\tconsole.log('last calc time', this.lastCalcTime);\n\t\t// console.log('nodesbyright', this.nodesByRight);\n\t\t// Uncomment this to test timeout on draws\n\t\t// this.lastCalcTime = 250;\n\t}\n\n\t// draw the links calculated from mapLinks()\n\tdrawLinks(ctx) {\n\t\tif (!this.canvas.default_connection_color_byType || !this.canvas.default_connection_color) {\n\t\t\tconsole.error('Missing canvas.default_connection_color_byType', this.canvas);\n\t\t\treturn;\n\t\t}\n\t\tif (this.debug)\n\t\t\tconsole.log('paths', this.paths);\n\n\t\tctx.save();\n\t\tconst currentNodeIds = this.canvas.selected_nodes || {};\n\t\tconst corners = [];\n\t\tthis.paths.filter((pathI) => {\n\t\t\tconst path = pathI.path;\n\t\t\tconst connection = pathI.node.outputs[pathI.slot];\n\t\t\tif (path.length <= 1) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tctx.beginPath();\n\t\t\tconst slotColor =\n\t\t\t\tthis.canvas.default_connection_color_byType[connection.type]\n\t\t\t\t|| this.canvas.default_connection_color.input_on;\n\n\t\t\tif (currentNodeIds[pathI.node.id] || currentNodeIds[pathI.targetNode.id]) {\n\t\t\t\tctx.strokeStyle = 'white';\n\t\t\t} else {\n\t\t\t\tctx.strokeStyle = slotColor;\n\t\t\t}\n\t\t\tctx.lineWidth = 3;\n\t\t\tconst cornerRadius = this.config.lineSpace;\n\n\t\t\tlet isPrevDotRound = false;\n\t\t\tfor (let p = 0; p < path.length; ++p) {\n\t\t\t\tconst pos = path[p];\n\n\t\t\t\tif (p === 0) {\n\t\t\t\t\tctx.moveTo(pos[0], pos[1]);\n\t\t\t\t}\n\t\t\t\tconst prevPos = pos;\n\t\t\t\tconst cornerPos = path[p + 1];\n\t\t\t\tconst nextPos = path[p + 2];\n\n\t\t\t\tlet drawn = false;\n\t\t\t\tif (nextPos) {\n\t\t\t\t\tconst xDiffBefore = cornerPos[0] - prevPos[0];\n\t\t\t\t\tconst yDiffBefore = cornerPos[1] - prevPos[1];\n\t\t\t\t\tconst xDiffAfter = nextPos[0] - cornerPos[0];\n\t\t\t\t\tconst yDiffAfter = nextPos[1] - cornerPos[1];\n\t\t\t\t\tconst isBeforeStraight = xDiffBefore === 0 || yDiffBefore === 0;\n\t\t\t\t\tconst isAfterStraight = xDiffAfter === 0 || yDiffAfter === 0;\n\t\t\t\t\t// up/down -> left/right\n\t\t\t\t\tif (\n\t\t\t\t\t\t(isBeforeStraight || isAfterStraight)\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst beforePos = [\n\t\t\t\t\t\t\tcornerPos[0],\n\t\t\t\t\t\t\tcornerPos[1],\n\t\t\t\t\t\t];\n\t\t\t\t\t\tconst afterPos = [\n\t\t\t\t\t\t\tcornerPos[0],\n\t\t\t\t\t\t\tcornerPos[1],\n\t\t\t\t\t\t];\n\n\t\t\t\t\t\tif (isBeforeStraight) {\n\t\t\t\t\t\t\tconst xSignBefore = Math.sign(xDiffBefore);\n\t\t\t\t\t\t\tconst ySignBefore = Math.sign(yDiffBefore);\n\t\t\t\t\t\t\tbeforePos[0] = cornerPos[0] - cornerRadius * xSignBefore;\n\t\t\t\t\t\t\tbeforePos[1] = cornerPos[1] - cornerRadius * ySignBefore;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (isAfterStraight) {\n\t\t\t\t\t\t\tconst xSignAfter = Math.sign(xDiffAfter);\n\t\t\t\t\t\t\tconst ySignAfter = Math.sign(yDiffAfter);\n\t\t\t\t\t\t\tafterPos[0] = cornerPos[0] + cornerRadius * xSignAfter;\n\t\t\t\t\t\t\tafterPos[1] = cornerPos[1] + cornerRadius * ySignAfter;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (isPrevDotRound\n\t\t\t\t\t\t\t&& Math.abs(isPrevDotRound[0] - beforePos[0]) <= cornerRadius\n\t\t\t\t\t\t\t&& Math.abs(isPrevDotRound[1] - beforePos[1]) <= cornerRadius\n\t\t\t\t\t\t) {\n//\t\t\t\t\t\t\t// if two rounded corners are too close, don't draw anything\n//\t\t\t\t\t\t\tctx.lineTo(cornerPos[0], cornerPos[1]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tctx.lineTo(beforePos[0], beforePos[1]);\n\t\t\t\t\t\t\tcorners.push(cornerPos);\n\t\t\t\t\t\t\tctx.quadraticCurveTo(cornerPos[0], cornerPos[1], afterPos[0], afterPos[1]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tisPrevDotRound = beforePos;\n\t\t\t\t\t\tdrawn = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (p > 0 && !drawn) {\n\t\t\t\t\tif (!isPrevDotRound) {\n\t\t\t\t\t\tctx.lineTo(pos[0], pos[1]);\n\t\t\t\t\t}\n\t\t\t\t\tisPrevDotRound = false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tctx.stroke();\n\t\t\tctx.closePath();\n\t\t\treturn false;\n\t\t});\n\n\t\tif (this.debug) {\n\t\t\tcorners.filter((corn) => {\n\t\t\t\tctx.strokeStyle = '#ff00ff';\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc(corn[0], corn[1], 1, 0, 2 * Math.PI);\n\t\t\t\tctx.stroke();\n\t\t\t\treturn false;\n\t\t\t});\n\n\t\t\tthis.nodesByRight.filter((nodeI) => {\n\t\t\t\tctx.lineWidth = 1;\n\t\t\t\tctx.strokeStyle = '#000080';\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.rect(\n\t\t\t\t\tnodeI.area[0],\n\t\t\t\t\tnodeI.area[1],\n\t\t\t\t\tnodeI.area[2] - nodeI.area[0],\n\t\t\t\t\tnodeI.area[3] - nodeI.area[1],\n\t\t\t\t);\n\t\t\t\tctx.stroke();\n\t\t\t\tctx.closePath();\n\n\t\t\t\tctx.strokeStyle = '#0000a0';\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.rect(\n\t\t\t\t\tnodeI.linesArea[0],\n\t\t\t\t\tnodeI.linesArea[1],\n\t\t\t\t\tnodeI.linesArea[2] - nodeI.linesArea[0],\n\t\t\t\t\tnodeI.linesArea[3] - nodeI.linesArea[1],\n\t\t\t\t);\n\t\t\t\tctx.stroke();\n\t\t\t\tctx.closePath();\n\t\t\t\treturn false;\n\t\t\t});\n\t\t}\n\n\t\tctx.restore();\n\t}\n}\n\n// pretend the subgraph in / out blocks to be normal nodes.\nclass SubgraphSlotProxy {\n\tconstructor(slot) {\n\t\tthis.slot = slot;\n\t}\n\n\tget links() {\n\t\treturn this.slot.linkIds;\n\t}\n}\n\nclass SubgraphInOutNodeProxy {\n\tconstructor(subgraphNode, isInput) {\n\t\tthis.subgraphNode = subgraphNode;\n\t\tthis.isInput = isInput;\n\t\tthis.slots = [];\n\t\tfor (const slot of this.subgraphNode.slots) {\n\t\t\tthis.slots.push(new SubgraphSlotProxy(slot));\n\t\t}\n\t}\n\n\tget id() {\n\t\treturn this.subgraphNode.id;\n\t}\n\n\tget outputs() {\n\t\tif (!this.isInput) {\n\t\t\t// output node in subgraph has no outputs, only inputs\n\t\t\treturn [];\n\t\t}\n\t\treturn this.slots;\n\t}\n\n\tgetSlotPosition(slot /* , isInput */) {\n\t\treturn this.subgraphNode.slots[slot].pos;\n\t}\n\n\tgetInputPos(slot) {\n\t\treturn this.getSlotPosition(slot, true);\n\t}\n\n\tgetOutputPos(slot) {\n\t\treturn this.getSlotPosition(slot, false);\n\t}\n\n\tgetBounding(area) {\n\t\tarea[0] = this.subgraphNode.boundingRect[0];\n\t\tarea[1] = this.subgraphNode.boundingRect[1];\n\t\tarea[2] = this.subgraphNode.boundingRect[2];\n\t\tarea[3] = this.subgraphNode.boundingRect[3];\n\t\treturn area;\n\t}\n}\n\nclass EyeButton {\n\tconstructor() {\n\t\tthis.hidden = null;\n\t}\n\n\tstatic getEyeButton() {\n\t\tconst eyeButtons = document.querySelectorAll('.pi-eye,.pi-eye-slash');\n\t\tif (eyeButtons.length > 1) {\n\t\t\tconsole.log('found too many eye buttons', eyeButtons);\n\t\t}\n\t\treturn eyeButtons[0];\n\t}\n\n\tcheck() {\n\t\tconst eyeButton = EyeButton.getEyeButton();\n\t\tif (!eyeButton) {\n\t\t\treturn;\n\t\t}\n\t\tconst hidden = eyeButton.classList.contains('pi-eye-slash');\n\t\tif (this.hidden !== hidden) {\n\t\t\tthis.hidden = hidden;\n\t\t\tif (this.onChange) {\n\t\t\t\tthis.onChange(hidden);\n\t\t\t}\n\t\t}\n\t}\n\n\tlistenEyeButton(onChange) {\n\t\tthis.onChange = onChange;\n\t\tconst eyeButton = EyeButton.getEyeButton();\n\t\tif (!eyeButton) {\n\t\t\tsetTimeout(() => this.listenEyeButton(onChange), 1000);\n\t\t\treturn;\n\t\t}\n\t\tconst eyeDom = eyeButton.parentNode;\n\t\teyeDom.addEventListener('click', () => this.check());\n\t\teyeDom.addEventListener('keyup', () => this.check());\n\t\teyeDom.addEventListener('mouseup', () => this.check());\n\t}\n}\n\nexport class CircuitBoardLines {\n\tconstructor() {\n\t\tthis.canvas = null;\n\t\tthis.mapLinks = null;\n\t\tthis.enabled = true;\n\t\tthis.eyeHidden = false;\n\t\tthis.maxDirectLineDistance = Number.MAX_SAFE_INTEGER;\n\t\tthis.config = {\n\t\t\tlineSpace : Math.floor(LiteGraph.NODE_SLOT_HEIGHT / 2),\n\t\t\tnodeSpace : [-8, -4, 12, 4],\n\t\t};\n\t}\n\n\tstatic cleanFloat(n, def) {\n\t\tn = parseFloat(n);\n\t\tif(isNaN(n)) {\n\t\t\tn = def;\n\t\t}\n\t\treturn n;\n\t}\n\n\tstatic cleanInteger(n, def) {\n\t\treturn Math.round(CircuitBoardLines.cleanFloat(n, def));\n\t}\n\n\tsetEnabled(e) { this.enabled = e; }\n\n\tisShow() { \n\t\treturn (\n\t\t\t!!this.enabled &&\n\t\t\t!this.eyeHidden &&\n\t\t\t(window?.app?.canvas?.links_render_mode >= 0)\n\t\t);\n\t}\n\n\trecalcMapLinksTimeout() {\n\t\t// calculate paths when user is idle...\n\t\tif (!this.skipNextRecalcTimeout) {\n\t\t\tif (this.recalcTimeout) {\n\t\t\t\tclearTimeout(this.recalcTimeout);\n\t\t\t\tthis.recalcTimeout = null;\n\t\t\t}\n\n\t\t\tthis.recalcTimeout = setTimeout(() => {\n\t\t\t\tthis.recalcTimeout = null;\n\t\t\t\tthis.recalcMapLinks();\n\t\t\t\tthis.redraw();\n\t\t\t}, this.mapLinks.lastCalcTime * 2);\n\t\t}\n\t\tthis.skipNextRecalcTimeout = false;\n\t}\n\n\tredraw() {\n\t\tif (this.lastDrawTimeout) {\n\t\t\tclearTimeout(this.lastDrawTimeout);\n\t\t\tthis.lastDrawTimeout = null;\n\t\t}\n\n\t\tthis.lastDrawTimeout = setTimeout(() => {\n\t\t\tthis.lastDrawTimeout = null;\n\t\t\twindow.requestAnimationFrame(() => {\n\t\t\t\tconsole.log('redraw timeout');\n\t\t\t\tthis.canvas.setDirty(true, true);\n\t\t\t\tthis.skipNextRecalcTimeout = true;\n\t\t\t\tthis.canvas.draw(true, true);\n\t\t\t});\n\t\t}, 0);\n\t}\n\n\trecalcMapLinksCheck() {\n\t\tif (this.mapLinks) {\n\t\t\tif (this.mapLinks.lastCalcTime > 100) {\n\t\t\t\tthis.recalcMapLinksTimeout();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tthis.recalcMapLinks();\n\t\treturn true;\n\t}\n\n\trecalcMapLinks() {\n\t\tthis.mapLinks = new MapLinks(this.canvas, this.config);\n\t\tthis.mapLinks.maxDirectLineDistance = this.maxDirectLineDistance;\n\t\tthis.mapLinks.debug = this.debug;\n\t\tconst nodesByExecution = this.canvas.graph.computeExecutionOrder() || [];\n\t\tif (this.canvas.subgraph) {\n\t\t\t// add subgraph nodes\n\t\t\tconst proxyInputNode = new SubgraphInOutNodeProxy(this.canvas.subgraph.inputNode, true);\n\t\t\tconst proxyOutputNode = new SubgraphInOutNodeProxy(this.canvas.subgraph.outputNode, false);\n\n\t\t\tnodesByExecution.push(proxyInputNode);\n\t\t\tnodesByExecution.push(proxyOutputNode);\n\t\t}\n\t\ttry {\n\t\t\tthis.mapLinks.mapLinks(nodesByExecution);\n\t\t} catch (e) {\n\t\t\tconsole.error('mapLinks error', e);\n\t\t}\n\t}\n\n\tdrawConnections(\n\t\tctx,\n\t) {\n\t\tif (!this.canvas || !this.canvas.graph) {\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.recalcMapLinksCheck();\n\n\t\t\tthis.mapLinks.drawLinks(ctx);\n\n\t\t} finally {\n\t\t\tthis.lastDrawConnections = new Date().getTime();\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tinit() {\n\t\tconst oldDrawConnections = LGraphCanvas.prototype.drawConnections;\n\t\tconst t = this;\n\t\tLGraphCanvas.prototype.drawConnections = function drawConnections(\n\t\t\tctx,\n\t\t) {\n\t\t\ttry {\n\t\t\t\tif (t.canvas && t.isShow()) {\n\t\t\t\t\treturn t.drawConnections(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tconsole.error(\"CircuitBoardLines.drawConnections Crash\");\n\t\t\t\tconsole.error(e);\n\t\t\t}\n\t\t\treturn oldDrawConnections.apply(this, arguments);\n\t\t};\n\t\tthis.eyeButton = new EyeButton();\n\t\tthis.eyeButton.listenEyeButton((hidden) => {\n\t\t\tthis.eyeHidden = hidden;\n\t\t});\n\t}\n\n\tinitOverrides(canvas) {\n\t\tthis.canvas = canvas;\n\t}\n}\n\n"
  },
  {
    "path": "js/QuickConnection.js",
    "content": "/* eslint no-tabs: 0 */\n/* eslint no-underscore-dangle:0 */\n/* eslint no-plusplus:0 */\n/* eslint prefer-rest-params:0 */\n/* eslint operator-linebreak:0 */\n/* eslint no-unneeded-ternary:0 */\n/* global LGraphCanvas */\n/* global LiteGraph */\n\nexport class QuickConnection {\n\tconstructor() {\n\t\tthis.insideConnection = null;\n\t\tthis.enabled = false;\n\t\t// use inputs that already have a link to them.\n\t\tthis.useInputsWithLinks = false;\n\t\tthis.release_link_on_empty_shows_menu = true;\n\t\tthis.connectDotOnly = true;\n\t\tthis.maxSuggestions = 15;\n\t\tthis.doNotAcceptType = /^\\*$/;\n\t\tthis.boxAlpha = 0.7;\n\t\tthis.boxBackground = '#000';\n\t\tthis.graph_mouse = [0, 0];\n\t}\n\n\tmouseUp(origThis, origFunc, args) {\n\t\tconst t = this;\n\t\tif (!t.enabled || !t.canvas) {\n\t\t\treturn origFunc.apply(origThis, args);\n\t\t}\n\n\t\t// Let's not popup the release on empty spot menu if we've released the mouse on a dot\n\t\tconst origReleaseLink = LiteGraph.release_link_on_empty_shows_menu;\n\t\tconst origShowConnectionMenu = t.canvas.showConnectionMenu;\n\n\t\tlet ret = null;\n\t\ttry {\n\t\t\tif (t.pointerUp()) {\n\t\t\t\tif (!t.isComfyUI) {\n\t\t\t\t\tLiteGraph.release_link_on_empty_shows_menu = false;\n\t\t\t\t} else {\n\t\t\t\t\tt.canvas.showConnectionMenu = () => {};\n\t\t\t\t}\n\t\t\t\tt.release_link_on_empty_shows_menu = false;\n\t\t\t}\n\t\t\tret = origFunc.apply(origThis, args);\n\t\t} finally {\n\t\t\tif (!t.release_link_on_empty_shows_menu) {\n\t\t\t\tif (!t.isComfyUI) {\n\t\t\t\t\tLiteGraph.release_link_on_empty_shows_menu = origReleaseLink;\n\t\t\t\t} else {\n\t\t\t\t\tt.canvas.showConnectionMenu = origShowConnectionMenu;\n\t\t\t\t\tt.canvas.linkConnector.reset();\n\t\t\t\t}\n\t\t\t\tt.release_link_on_empty_shows_menu = true;\n\t\t\t}\n\t\t}\n\t\treturn ret;\n\t}\n\n\tinit() {\n\t\tconst t = this;\n\t\tconst origProcessMouseDown = LGraphCanvas.prototype.processMouseDown;\n\t\tthis.acceptingNodes = null;\n\t\tLGraphCanvas.prototype.processMouseDown = function mouseDown() {\n\t\t\tt.pointerDown();\n\t\t\tconst ret = origProcessMouseDown.apply(this, arguments);\n\t\t\treturn ret;\n\t\t};\n\n\t\tconst origProcessMouseUp = LGraphCanvas.prototype.processMouseUp;\n\t\tLGraphCanvas.prototype.processMouseUp = function newProcessMouseUp() {\n\t\t\treturn t.mouseUp(this, origProcessMouseUp, arguments);\n\t\t};\n\n\t\t// ComfyUI has it's own version of litegraph.js\n\t\t// https://github.com/Comfy-Org/litegraph.js\n\t}\n\n\tsetGraphMouseFromClientXY(clientX, clientY) {\n\t\tif (!this.canvas) {\n\t\t\treturn;\n\t\t}\n\t\tconst { offset, scale } = this.canvas.ds;\n\t\tconst boundingRect = this.canvas.canvas.getBoundingClientRect();\n\t\tthis.graph_mouse = [\n\t\t\t(clientX - boundingRect.x) / scale - offset[0],\n\t\t\t(clientY - boundingRect.y) / scale - offset[1],\n\t\t];\n\t}\n\n\tinitListeners(canvas) {\n\t\tconst t = this;\n\t\tthis.enabled = true;\n\t\tthis.graph = canvas.graph;\n\t\tthis.canvas = canvas;\n\t\tif (!this.canvas.canvas) {\n\t\t\tconsole.error('no canvas', this.canvas);\n\t\t} else {\n\t\t\tthis.canvas.canvas.addEventListener('litegraph:canvas', (e) => {\n\t\t\t\tconst { detail } = e;\n\t\t\t\tif (!this.release_link_on_empty_shows_menu\n\t\t\t\t\t&& detail && detail.subType === 'empty-release'\n\t\t\t\t) {\n\t\t\t\t\te.stopPropagation();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tthis.isComfyUI = this.canvas.connecting_links !== undefined ? true : false;\n\n\t\tthis.addOnCanvas('onDrawOverlay', (ctx) => {\n\t\t\ttry {\n\t\t\t\tthis.onDrawOverlay(ctx);\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error('onDrawOverlayCrash', e, ctx);\n\t\t\t}\n\t\t});\n\n\t\t// src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts:onPointerDown\n\t\t// uses useEventListener(..., { capture: true } )\n\t\t// Which stops the mousemove / pointermove event from working.\n\t\t//\n\t\tlet origEventsReplacementId = 1;\n\t\tconst origEventsReplacement = {};\n\t\tif (LiteGraph.vueNodesMode) {\n\t\t\tconst oldAddEventListener = window.addEventListener;\n\t\t\tconst uncaptureEvents = {\n\t\t\t\tpointermove: true,\n\t\t\t\tpointerup: true,\n\t\t\t};\n\t\t\twindow.addEventListener = function newAddEventListener(name, func, opts) {\n\t\t\t\tif (uncaptureEvents[name] && opts?.capture) {\n\t\t\t\t\tconst newArgs = Array.from(arguments);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst newOpts = { ...opts };\n\t\t\t\t\t\tnewArgs[2] = newOpts;\n\t\t\t\t\t\tconst origEventFunc = newArgs[1];\n\t\t\t\t\t\tlet newFunc = null;\n\t\t\t\t\t\tif (name === 'pointerup') {\n\t\t\t\t\t\t\tnewFunc = function newPointerUp() {\n\t\t\t\t\t\t\t\treturn t.mouseUp(this, origEventFunc, arguments);\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t} else if (name === 'pointermove') {\n\t\t\t\t\t\t\tnewFunc = function newPointerMove(e) {\n\t\t\t\t\t\t\t\tt.setGraphMouseFromClientXY(e.clientX, e.clientY);\n\t\t\t\t\t\t\t\treturn origEventFunc.apply(this, arguments);\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (newFunc) {\n\t\t\t\t\t\t\tnewArgs[1] = newFunc;\n\t\t\t\t\t\t\torigEventFunc.__id = ++origEventsReplacementId;\n\t\t\t\t\t\t\torigEventsReplacement[origEventFunc.__id] = newFunc;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tconsole.error('QuickConnections.newAddEventListener crash');\n\t\t\t\t\t\tconsole.error(e);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn oldAddEventListener.apply(window, newArgs);\n\t\t\t\t}\n\t\t\t\treturn oldAddEventListener.apply(window, arguments);\n\t\t\t};\n\t\t\tconst oldRemoveEventListener = window.removeEventListener;\n\t\t\twindow.removeEventListener = function newRemoveEventListener() {\n\t\t\t\tconst newArgs = [...arguments];\n\t\t\t\ttry {\n\t\t\t\t\tconst funcId = arguments[1].__id;\n\t\t\t\t\tif (funcId) {\n\t\t\t\t\t\tconst newEventFunc = origEventsReplacement[funcId];\n\t\t\t\t\t\tif (!newEventFunc) {\n\t\t\t\t\t\t\tconsole.warn('Could not find replaced event to remove, id:', funcId, arguments);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnewArgs[1] = newEventFunc;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.error('QuickConnections.newAddEventListener crash');\n\t\t\t\t\tconsole.error(e);\n\t\t\t\t}\n\t\t\t\treturn oldRemoveEventListener.apply(window, newArgs);\n\t\t\t};\n\t\t}\n\n\t\tthis.canvas.canvas.addEventListener('pointermove', (e) => {\n\t\t\tthis.graph_mouse[0] = e.canvasX;\n\t\t\tthis.graph_mouse[1] = e.canvasY;\n\t\t});\n\t}\n\n\t// Get the current connection that we are dragging\n\tgetCurrentConnection() {\n\t\tif (this.isComfyUI) {\n\t\t\tconst connectingLink =\n\t\t\t\t(this.canvas.connecting_links\n\t\t\t\t\t&& this.canvas.connecting_links.length > 0\n\t\t\t\t) ?\n\t\t\t\t\tthis.canvas.connecting_links[0] : null;\n\t\t\tif (connectingLink) {\n\t\t\t\treturn {\n\t\t\t\t\tnode: connectingLink.node,\n\t\t\t\t\tslot: connectingLink.slot,\n\t\t\t\t\tinput: connectingLink.input,\n\t\t\t\t\toutput: connectingLink.output,\n\t\t\t\t\tpos: connectingLink.pos,\n\t\t\t\t};\n\t\t\t}\n\t\t} else if (this.canvas.connecting_node) {\n\t\t\treturn {\n\t\t\t\tnode: this.canvas.connecting_node,\n\t\t\t\tinput: this.canvas.connecting_input,\n\t\t\t\tslot: this.canvas.connecting_slot,\n\t\t\t\toutput: this.canvas.connecting_output,\n\t\t\t};\n\t\t}\n\t\treturn null;\n\t}\n\n\tpointerDown() {\n\t\tthis.acceptingNodes = null;\n\t\treturn false;\n\t}\n\n\tpointerUp() {\n\t\tthis.acceptingNodes = null;\n\t\tconst connectionInfo = this.getCurrentConnection();\n\n\t\tif (this.insideConnection && connectionInfo) {\n\t\t\tif (connectionInfo.input) {\n\t\t\t\tif (connectionInfo.node.connect) {\n\t\t\t\t\tthis.insideConnection.node.connect(\n\t\t\t\t\t\tthis.insideConnection.connection_slot_index,\n\t\t\t\t\t\tconnectionInfo.node,\n\t\t\t\t\t\tconnectionInfo.slot,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\t// subgraph\n\t\t\t\t\tconnectionInfo.input.connect(\n\t\t\t\t\t\tthis.insideConnection.node.outputs[this.insideConnection.connection_slot_index],\n\t\t\t\t\t\tthis.insideConnection.node,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// output\n\t\t\t\tif (connectionInfo.node.connect) {\n\t\t\t\t\tconnectionInfo.node.connect(\n\t\t\t\t\t\tconnectionInfo.slot,\n\t\t\t\t\t\tthis.insideConnection.node,\n\t\t\t\t\t\tthis.insideConnection.connection_slot_index,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\t// subgraph\n\t\t\t\t\tconnectionInfo.output.connect(\n\t\t\t\t\t\tthis.insideConnection.node.inputs[this.insideConnection.connection_slot_index],\n\t\t\t\t\t\tthis.insideConnection.node,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t// Find the list of nodes we can drag to\n\tfindAcceptingNodes(fromConnection, fromNode, findInput) {\n\t\tconst accepting = [];\n\t\tif (this.doNotAcceptType.exec(fromConnection.type)) {\n\t\t\t// Too many connections are available if we area a * connection\n\t\t\treturn accepting;\n\t\t}\n\t\tconst addToAccepting = (arr, node) => {\n\t\t\tif (node.mode == 4) {\n\t\t\t\t// bypassed\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (node.id === fromNode.id) {\n\t\t\t\t// Don't connect to myself\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (let c = 0; c < arr.length; ++c) {\n\t\t\t\tconst input = arr[c];\n\t\t\t\tif (!input.link || this.useInputsWithLinks) {\n\t\t\t\t\tconst accept = LiteGraph.isValidConnection(\n\t\t\t\t\t\tinput.type,\n\t\t\t\t\t\tfromConnection.type,\n\t\t\t\t\t);\n\t\t\t\t\tif (accept && !this.doNotAcceptType.exec(input.type)) {\n\t\t\t\t\t\taccepting.push({\n\t\t\t\t\t\t\tnode,\n\t\t\t\t\t\t\tconnection: input,\n\t\t\t\t\t\t\tconnection_slot_index: c,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst nodes = this.canvas.subgraph?._nodes || this.graph._nodes;\n\t\tfor (let i = 0; i < nodes.length; ++i) {\n\t\t\tconst node = nodes[i];\n\t\t\tif (node.inputs && findInput) {\n\t\t\t\taddToAccepting(node.inputs, node);\n\t\t\t}\n\t\t\tif (node.outputs && !findInput) {\n\t\t\t\taddToAccepting(node.outputs, node);\n\t\t\t}\n\t\t}\n\n\t\taccepting.sort((a, b) => a.node.pos[1] - b.node.pos[1]);\n\t\tif (this.maxSuggestions) {\n\t\t\treturn accepting.slice(0, this.maxSuggestions);\n\t\t}\n\t\treturn accepting;\n\t}\n\n\taddOnCanvas(name, func) {\n\t\tconst obj = this.canvas;\n\t\tconst oldFunc = obj[name];\n\t\tobj[name] = function callFunc() {\n\t\t\tif (oldFunc) {\n\t\t\t\toldFunc.apply(obj, arguments);\n\t\t\t}\n\t\t\treturn func.apply(obj, arguments);\n\t\t};\n\t}\n\n\tonDrawOverlay(ctx) {\n\t\tif (!this.enabled) {\n\t\t\treturn;\n\t\t}\n\t\tif (!this.canvas) {\n\t\t\tconsole.error('no canvas or mouse yet', this.canvas);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.insideConnection = null;\n\n\t\tconst connectionInfo = this.getCurrentConnection();\n\n\t\tif (connectionInfo) {\n\t\t\tconst {\n\t\t\t\tnode, input, output, slot,\n\t\t\t} = connectionInfo;\n\t\t\tif (!input && !output) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// const slotPos = new Float32Array(2);\n\n\t\t\tconst isInput = input ? true : false;\n\t\t\tconst connecting = isInput ? input : output;\n\t\t\tconst connectionSlot = slot;\n\n\t\t\tlet pos;\n\t\t\tif (!node.getOutputPos || !node.getInputPos) {\n\t\t\t\tpos = connectionInfo.pos;\n\t\t\t} else if (LiteGraph.vueNodesMode && node.getSlotPosition) {\n\t\t\t\tpos = node.getSlotPosition(\n\t\t\t\t\tconnectionSlot,\n\t\t\t\t\tisInput,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpos = isInput ?\n\t\t\t\t\tnode.getInputPos(connectionSlot)\n\t\t\t\t\t: node.getOutputPos(connectionSlot);\n\t\t\t}\n\t\t\tif (!this.acceptingNodes) {\n\t\t\t\tthis.acceptingNodes = this.findAcceptingNodes(\n\t\t\t\t\tconnecting,\n\t\t\t\t\t// this.canvas.connecting_node,\n\t\t\t\t\tnode,\n\t\t\t\t\t!isInput,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst mouseX = this.graph_mouse[0];\n\t\t\tconst mouseY = this.graph_mouse[1];\n\n\t\t\t// const hasNodeTooltip = document.querySelector('.node-tooltip');\n\n\t\t\tconst buttonShift = [\n\t\t\t\tisInput ? -32 : +32,\n\t\t\t\t// 2025-08-29: tooltip seems to be far away now, don't change position\n\t\t\t\t0,\n\t\t\t\t/*\n\t\t\t\t(true || this.acceptingNodes.length === 1 || hasNodeTooltip)\n\t\t\t\t\t? 0\n\t\t\t\t\t: (\n\t\t\t\t\t\t((-this.acceptingNodes.length * LiteGraph.NODE_SLOT_HEIGHT) / 2)\n\t\t\t\t\t\t+ (LiteGraph.NODE_SLOT_HEIGHT / 2)\n\t\t\t\t\t),\n\t\t\t\t\t*/\n\t\t\t];\n\t\t\tconst linkPos = [\n\t\t\t\tpos[0] + buttonShift[0],\n\t\t\t\tpos[1] + buttonShift[1],\n\t\t\t];\n\n\t\t\tlet scale = 1 / this.canvas.ds.scale;\n\t\t\tif (scale < 1.0) {\n\t\t\t\tscale = 1.0;\n\t\t\t}\n\n\t\t\tconst linkCloseArea = [\n\t\t\t\tlinkPos[0] - (LiteGraph.NODE_SLOT_HEIGHT * 6 * scale),\n\t\t\t\tlinkPos[1] - LiteGraph.NODE_SLOT_HEIGHT,\n\t\t\t\tLiteGraph.NODE_SLOT_HEIGHT * 8 * scale,\n\t\t\t\tLiteGraph.NODE_SLOT_HEIGHT * (this.acceptingNodes.length + 1) * scale,\n\t\t\t];\n\t\t\tif (!isInput) {\n\t\t\t\tlinkCloseArea[0] = linkPos[0] - ((LiteGraph.NODE_SLOT_HEIGHT * 2) * scale);\n\t\t\t}\n\n\t\t\tconst isInsideClosePosition = LiteGraph.isInsideRectangle(\n\t\t\t\tmouseX,\n\t\t\t\tmouseY,\n\t\t\t\tlinkCloseArea[0],\n\t\t\t\tlinkCloseArea[1],\n\t\t\t\tlinkCloseArea[2],\n\t\t\t\tlinkCloseArea[3],\n\t\t\t);\n\t\t\tlet boxRect = null;\n\t\t\tconst textsToDraw = [];\n\n\t\t\tctx.save();\n\t\t\tthis.canvas.ds.toCanvasContext(ctx);\n\n\t\t\t// const oldFillStyle = ctx.fillStyle;\n\t\t\tif (isInsideClosePosition) {\n\t\t\t\t// We are inside the spot next to the node\n\t\t\t\t//  where we can release the mouse and connect to something\n\t\t\t\tconst oldFont = ctx.font;\n\t\t\t\tlet font = oldFont;\n\t\t\t\tconst fontM = /([0-9]+)px/.exec(font);\n\t\t\t\tif (!fontM) {\n\t\t\t\t\tfontM[1] = 'px';\n\t\t\t\t\tfont += ' 12px';\n\t\t\t\t}\n\t\t\t\tif (fontM) {\n\t\t\t\t\tconst fontSize = parseInt(fontM[1], 10) * scale;\n\t\t\t\t\tctx.font = font.replace(/[0-9]+px/, `${fontSize}px`);\n\t\t\t\t}\n\t\t\t\tthis.acceptingNodes.filter((acceptingNode) => {\n\t\t\t\t\tconst textxy = [\n\t\t\t\t\t\tlinkPos[0] + (isInput ? -LiteGraph.NODE_SLOT_HEIGHT : LiteGraph.NODE_SLOT_HEIGHT),\n\t\t\t\t\t\tlinkPos[1],\n\t\t\t\t\t];\n\n\t\t\t\t\tconst acceptingText = `${acceptingNode.connection.name} @${acceptingNode.node.title}`;\n\t\t\t\t\tconst textBox = ctx.measureText(acceptingText);\n\t\t\t\t\tconst box = [\n\t\t\t\t\t\ttextxy[0],\n\t\t\t\t\t\ttextxy[1] - textBox.fontBoundingBoxAscent,\n\t\t\t\t\t\ttextBox.width,\n\t\t\t\t\t\tLiteGraph.NODE_SLOT_HEIGHT,\n\t\t\t\t\t\t// (textBox.fontBoundingBoxAscent + textBox.fontBoundingBoxDescent),\n\t\t\t\t\t];\n\n\t\t\t\t\tlet textAlign;\n\t\t\t\t\tif (!isInput) {\n\t\t\t\t\t\ttextAlign = 'left';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbox[0] -= textBox.width;\n\t\t\t\t\t\ttextAlign = 'right';\n\t\t\t\t\t}\n\n\t\t\t\t\tconst rRect = [\n\t\t\t\t\t\tbox[0] - 8 * scale,\n\t\t\t\t\t\tbox[1] - 4 * scale,\n\t\t\t\t\t\tbox[2] + 16 * scale,\n\t\t\t\t\t\tbox[3], // + 5 * scale,\n\t\t\t\t\t];\n\t\t\t\t\tif (!boxRect) {\n\t\t\t\t\t\tboxRect = rRect.slice(0);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (boxRect[0] > rRect[0]) {\n\t\t\t\t\t\t\tboxRect[0] = rRect[0];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (boxRect[2] < rRect[2]) {\n\t\t\t\t\t\t\tboxRect[2] = rRect[2];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tboxRect[3] += rRect[3];\n\t\t\t\t\t}\n\n\t\t\t\t\ttextsToDraw.push({\n\t\t\t\t\t\tx: textxy[0],\n\t\t\t\t\t\ty: textxy[1],\n\t\t\t\t\t\tacceptingText,\n\t\t\t\t\t\ttextAlign,\n\t\t\t\t\t});\n\n\t\t\t\t\tlet isInsideRect;\n\t\t\t\t\tif (this.connectDotOnly) {\n\t\t\t\t\t\tisInsideRect = LiteGraph.isInsideRectangle(\n\t\t\t\t\t\t\tmouseX,\n\t\t\t\t\t\t\tmouseY,\n\t\t\t\t\t\t\tlinkPos[0] - ((LiteGraph.NODE_SLOT_HEIGHT / 2) * scale),\n\t\t\t\t\t\t\tlinkPos[1] - ((LiteGraph.NODE_SLOT_HEIGHT / 2) * scale),\n\t\t\t\t\t\t\tLiteGraph.NODE_SLOT_HEIGHT * scale,\n\t\t\t\t\t\t\tLiteGraph.NODE_SLOT_HEIGHT * scale,\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tisInsideRect = LiteGraph.isInsideRectangle(\n\t\t\t\t\t\t\tmouseX,\n\t\t\t\t\t\t\tmouseY,\n\t\t\t\t\t\t\tisInput ? box[0] : (linkPos[0] - (LiteGraph.NODE_SLOT_HEIGHT / 2)),\n\t\t\t\t\t\t\tlinkPos[1] - 10,\n\t\t\t\t\t\t\tisInput ?\n\t\t\t\t\t\t\t\t((linkPos[0] - box[0]) + LiteGraph.NODE_SLOT_HEIGHT / 2)\n\t\t\t\t\t\t\t\t: (rRect[2] + LiteGraph.NODE_SLOT_HEIGHT / 2),\n\t\t\t\t\t\t\trRect[3],\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (isInsideRect && !this.insideConnection) {\n\t\t\t\t\t\tthis.insideConnection = acceptingNode;\n\t\t\t\t\t\tctx.fillStyle = LiteGraph.EVENT_LINK_COLOR; // \"#ffcc00\";\n\t\t\t\t\t\t// highlight destination if mouseover\n\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\tctx.arc(\n\t\t\t\t\t\t\tlinkPos[0],\n\t\t\t\t\t\t\tlinkPos[1],\n\t\t\t\t\t\t\t6 * scale,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tMath.PI * 2,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\tctx.closePath();\n\n\t\t\t\t\t\tctx.beginPath();\n\n\t\t\t\t\t\tctx.strokeStyle = '#6a6';\n\t\t\t\t\t\tctx.setLineDash([5, 10]);\n\t\t\t\t\t\tctx.lineWidth = 3;\n\n\t\t\t\t\t\tconst aNode = acceptingNode.node;\n\t\t\t\t\t\t// const destPos = new Float32Array(2);\n\t\t\t\t\t\tif (!aNode?.getOutputPos || !aNode.getInputPos) {\n\t\t\t\t\t\t\tconsole.warn('Node has no getInputPos/getOutputPos', aNode);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst destPos = isInput ?\n\t\t\t\t\t\t\t\taNode.getOutputPos(acceptingNode.connection_slot_index)\n\t\t\t\t\t\t\t\t: aNode.getInputPos(acceptingNode.connection_slot_index);\n\t\t\t\t\t\t\tctx.moveTo(pos[0], pos[1]);\n\n\t\t\t\t\t\t\tctx.lineTo(destPos[0], destPos[1]);\n\t\t\t\t\t\t\tctx.stroke();\n\t\t\t\t\t\t\tctx.closePath();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst slotColor =\n\t\t\t\t\t\t\tthis.canvas.default_connection_color_byType[acceptingNode.connection.type]\n\t\t\t\t\t\t\t|| this.canvas.default_connection_color.input_on;\n\n\t\t\t\t\t\tctx.fillStyle = slotColor || this.canvas.default_connection_color.input_on;\n\t\t\t\t\t\tctx.beginPath();\n\n\t\t\t\t\t\tctx.arc(linkPos[0], linkPos[1], 4 * scale, 0, Math.PI * 2);\n\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\tctx.closePath();\n\t\t\t\t\t}\n\n\t\t\t\t\tlinkPos[1] += LiteGraph.NODE_SLOT_HEIGHT * scale;\n\t\t\t\t\treturn false;\n\t\t\t\t});\n\n\t\t\t\tif (boxRect) {\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.fillStyle = this.boxBackground;\n\t\t\t\t\tconst oldAlpha = ctx.globalAlpha;\n\t\t\t\t\tctx.globalAlpha = this.boxAlpha;\n\t\t\t\t\tctx.roundRect(\n\t\t\t\t\t\tboxRect[0],\n\t\t\t\t\t\tboxRect[1],\n\t\t\t\t\t\tboxRect[2],\n\t\t\t\t\t\tboxRect[3],\n\t\t\t\t\t\t5,\n\t\t\t\t\t);\n\t\t\t\t\tctx.fill();\n\t\t\t\t\tctx.closePath();\n\t\t\t\t\tctx.globalAlpha = oldAlpha;\n\t\t\t\t}\n\n\t\t\t\tctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;\n\t\t\t\ttextsToDraw.filter((textToDraw) => {\n\t\t\t\t\tctx.textAlign = textToDraw.textAlign;\n\t\t\t\t\tctx.fillText(textToDraw.acceptingText, textToDraw.x, textToDraw.y);\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\n\t\t\t\tctx.font = oldFont;\n\t\t\t}\n\t\t\tctx.restore();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "js/quick_conn_start.js",
    "content": "/* eslint quotes:0 */\n/* eslint prefer-spread:0 */\n/* global LiteGraph */\n/* eslint operator-linebreak:0 */\n\nimport { app } from \"../../scripts/app.js\";\nimport { QuickConnection } from \"./QuickConnection.js\";\nimport { CircuitBoardLines } from './CircuitBoardLines.js';\n\nconst quickConnection = new QuickConnection();\nquickConnection.init();\n\nconst quickConnectionId = \"quick-connections\";\n\nconst quickConnectionsExt = {\n\tname: \"Quick Connections\",\n\tsettings: [\n\t\t{\n\t\t\tid: `${quickConnectionId}.enable`,\n\t\t\tname: \"Quick connections enable\",\n\t\t\ttype: \"boolean\",\n\t\t\tdefaultValue: true,\n\t\t\tonChange: (...args) => {\n\t\t\t\t[quickConnection.enabled] = args;\n\t\t\t\tif (app?.graph?.change) {\n\t\t\t\t\treturn app.graph.change.apply(app.graph, args);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: `${quickConnectionId}.connectDotOnly`,\n\t\t\tcategory: [quickConnectionId, \"enable\", \"connectDotOnly\"],\n\t\t\tname: \"Connect with dot\",\n\t\t\ttooltip: \"Disable to connect with text too, a bigger area to release the mouse button\",\n\t\t\ttype: \"boolean\",\n\t\t\tdefaultValue: true,\n\t\t\tonChange: (...args) => {\n\t\t\t\t[quickConnection.connectDotOnly] = args;\n\t\t\t\tif (app?.graph?.change) {\n\t\t\t\t\treturn app.graph.change.apply(app.graph, args);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: `${quickConnectionId}.maxSuggestions`,\n\t\t\tcategory: [quickConnectionId, \"enable\", \"maxSuggestions\"],\n\t\t\tname: \"Maximum suggestions for connections\",\n\t\t\ttype: \"number\",\n\t\t\tdefaultValue: 15,\n\t\t\tonChange: (...args) => {\n\t\t\t\t[quickConnection.maxSuggestions] = args;\n\t\t\t\tif (app?.graph?.change) {\n\t\t\t\t\treturn app.graph.change.apply(app.graph, args);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t},\n\t\t},\n\t],\n\n\tinit() {\n\t\tquickConnection.initListeners(app.canvas);\n\t},\n};\n\nconst circuitBoardLines = new CircuitBoardLines();\nconst circuitBoardId = \"circuit-board-lines\";\nconst defaultConfig = {\n\tnodeSpace: [-8, -4, 12, 4],\n\tlineSpace: Math.floor(LiteGraph.NODE_SLOT_HEIGHT / 2),\n};\n\n\nconst circuitBoardLinesExt = {\n\tname: \"Circuit Board Lines\",\n\n\tsettings: [\n\t\t{\n\t\t\tid: `${circuitBoardId}.enable`,\n\t\t\tname: \"Circuit Board lines\",\n\t\t\tcategory: [circuitBoardId, \"enable\", \"enable\"],\n\t\t\ttype: \"combo\",\n\t\t\toptions: [\n\t\t\t\t{ value: 0, text: \"Off\" },\n\t\t\t\t{ value: 1, text: \"Circuit board\" },\n\t\t\t\t// On top doesn't place the wires on top of the text boxes\n\t\t\t\t{ value: 2, text: \"On top (Does not work in nodes v2)\" },\n\t\t\t],\n\t\t\tdefaultValue: 1,\n\n\t\t\tonChange: (...args) => {\n\t\t\t\tconst option = args[0];\n\t\t\t\tcircuitBoardLines.enabled = (option === 1);\n\t\t\t\tif (app.graph) {\n\t\t\t\t\tapp.graph.config.links_ontop = (option === 2);\n\t\t\t\t\tif (app.canvas?.graph?.config) {\n\t\t\t\t\t\tapp.canvas.graph.config.links_ontop = (option === 2);\n\t\t\t\t\t}\n\t\t\t\t\treturn app.graph.change.apply(app.graph, args);\n\t\t\t\t}\n\t\t\t\tconsole.error('app.graph not available');\n\t\t\t\treturn null;\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: `${circuitBoardId}.spacing-left`,\n\t\t\tname: \"Node spacing left\",\n\t\t\tcategory: [circuitBoardId, \"spacing\",\"left\"],\n\t\t\ttooltip: \"Spacing between line and left of node\",\n\t\t\ttype: \"number\",\n\t\t\t// 8,4,12,4\n\t\t\tdefaultValue: defaultConfig.nodeSpace[0],\n\t\t\tonChange: (...args) => {\n\t\t\t\tcircuitBoardLines.config.nodeSpace[0] =\n\t\t\t\t\tCircuitBoardLines.cleanInteger(args[0], defaultConfig.nodeSpace[0]);\n\n\t\t\t\tif (app.graph) {\n\t\t\t\t\treturn app.graph.change.apply(app.graph, args);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: `${circuitBoardId}.spacing-top`,\n\t\t\tname: \"Node spacing top\",\n\t\t\tcategory: [circuitBoardId, \"spacing\",\"top\"],\n\t\t\ttooltip: \"Spacing between line and top of node\",\n\t\t\ttype: \"number\",\n\t\t\t// 8,4,12,4\n\t\t\tdefaultValue: defaultConfig.nodeSpace[1],\n\t\t\tonChange: (...args) => {\n\t\t\t\tcircuitBoardLines.config.nodeSpace[1] =\n\t\t\t\t\tCircuitBoardLines.cleanInteger(args[0], defaultConfig.nodeSpace[1]);\n\n\t\t\t\tif (app.graph) {\n\t\t\t\t\treturn app.graph.change.apply(app.graph, args);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: `${circuitBoardId}.spacing-right`,\n\t\t\tname: \"Node spacing right\",\n\t\t\tcategory: [circuitBoardId, \"spacing\",\"right\"],\n\t\t\ttooltip: \"Spacing between line and right of node\",\n\t\t\ttype: \"number\",\n\t\t\tdefaultValue: defaultConfig.nodeSpace[2],\n\t\t\tonChange: (...args) => {\n\t\t\t\tcircuitBoardLines.config.nodeSpace[2] =\n\t\t\t\t\tCircuitBoardLines.cleanInteger(args[0], defaultConfig.nodeSpace[2]);\n\n\t\t\t\tif (app.graph) {\n\t\t\t\t\treturn app.graph.change.apply(app.graph, args);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: `${circuitBoardId}.spacingbottom`,\n\t\t\tname: \"Node spacing bottom\",\n\t\t\tcategory: [circuitBoardId, \"spacing\",\"bottom\"],\n\t\t\ttooltip: \"Spacing between line and bottom of node\",\n\t\t\ttype: \"number\",\n\t\t\tdefaultValue: defaultConfig.nodeSpace[3],\n\t\t\tonChange: (...args) => {\n\t\t\t\tcircuitBoardLines.config.nodeSpace[3] =\n\t\t\t\t\tCircuitBoardLines.cleanInteger(args[0], defaultConfig.nodeSpace[3]);\n\n\t\t\t\tif (app.graph) {\n\t\t\t\t\treturn app.graph.change.apply(app.graph, args);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: `${circuitBoardId}.spacing-line`,\n\t\t\tname: \"Line spacing\",\n\t\t\tcategory: [circuitBoardId, \"spacing\", \"lines\"],\n\t\t\ttooltip: \"Spacing between lines\",\n\t\t\ttype: \"number\",\n\t\t\tdefaultValue: defaultConfig.lineSpace,\n\t\t\tonChange: (...args) => {\n\t\t\t\tcircuitBoardLines.config.lineSpace =\n\t\t\t\t\t\tCircuitBoardLines.cleanInteger(args[0], defaultConfig.lineSpace)\n\t\t\t\tif (app.graph) {\n\t\t\t\t\treturn app.graph.change.apply(app.graph, args);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: `${circuitBoardId}.only90or45`,\n\t\t\tname: \"Prefer 90 or 45 degree lines\",\n\t\t\tcategory: [circuitBoardId, \"enable\", \"only90or45\"],\n\t\t\ttooltip: \"Show mostly 90 or 45 degree lines, normally it'll link directly at any angle if the line if there are no nodes in the way\",\n\t\t\ttype: \"boolean\",\n\t\t\tdefaultValue: true,\n\t\t\tonChange: (...args) => {\n\t\t\t\tcircuitBoardLines.maxDirectLineDistance = args[0]\n\t\t\t\t\t? 20 : Number.MAX_SAFE_INTEGER;\n\n\t\t\t\tif (app.graph) {\n\t\t\t\t\treturn app.graph.change.apply(app.graph, args);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t},\n\t\t},\n\t],\n\n\tinit() {\n\t\tcircuitBoardLines.init();\n\t\tcircuitBoardLines.initOverrides(app.canvas);\n\t},\n};\n\napp.registerExtension(quickConnectionsExt);\napp.registerExtension(circuitBoardLinesExt);\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"example\": \"bash example/run_example.sh\",\n\t\t\"docs\": \"bash example/make_docs.sh\",\n\t\t\"lint\": \"eslint js/*.js example/*quick_conn.js\"\n\t},\n\t\"dependencies\": {\n\t\t\"@comfyorg/litegraph\": \">=0.7.47\",\n\t\t\"litegraph.js\": \">=0.7.18\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@eslint/js\": \"^10.0.1\",\n\t\t\"eslint\": \"^10.2.0\"\n\t}\n}\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"quick-connections\"\ndescription = \"Quick connections, Circuit board connections\"\nversion = \"1.0.29\"\nlicense = {text = \"MIT License\"}\n\n[project.urls]\nRepository = \"https://github.com/niknah/quick-connections\"\n#  Used by Comfy Registry https://comfyregistry.org\n\n[tool.comfy]\nPublisherId = \"niknah\"\nDisplayName = \"quick-connections\"\nIcon = \"\"\n"
  }
]